diff --git a/browser/src/Editor/BufferManager.ts b/browser/src/Editor/BufferManager.ts index f5419aba2d..dc183d3e2b 100644 --- a/browser/src/Editor/BufferManager.ts +++ b/browser/src/Editor/BufferManager.ts @@ -338,25 +338,29 @@ export class Buffer implements IBuffer { public handleInput(key: string): boolean { const state = this._store.getState() - const layers: IBufferLayer[] = state.layers[this._id] + const bufferLayers: IBufferLayer[] = state.layers[this._id] - if (!layers || !layers.length) { + if (!bufferLayers || !bufferLayers.length) { return false } - const result = layers.reduce((prev, curr) => { - if (prev) { - return true - } + const layerShouldHandleInput = bufferLayers.reduce( + (layerHandlerExists, currentLayer) => { + if (layerHandlerExists) { + return true + } - if (!curr || !curr.handleInput) { + if (!currentLayer || !currentLayer.handleInput) { + return false + } else if (currentLayer.isActive && currentLayer.isActive()) { + return currentLayer.handleInput(key) + } return false - } else { - return curr.handleInput(key) - } - }, false) + }, + false, + ) - return result + return layerShouldHandleInput } public async updateHighlights( tokenColors: TokenColor[], diff --git a/browser/src/Editor/NeovimEditor/BufferLayerManager.ts b/browser/src/Editor/NeovimEditor/BufferLayerManager.ts index c3f504a996..9c9b965a26 100644 --- a/browser/src/Editor/NeovimEditor/BufferLayerManager.ts +++ b/browser/src/Editor/NeovimEditor/BufferLayerManager.ts @@ -11,6 +11,7 @@ export type BufferFilter = (buf: Oni.Buffer) => boolean export interface IBufferLayer extends Oni.BufferLayer { handleInput?: (key: string) => boolean + isActive?: () => boolean } export const createBufferFilterFromLanguage = (language: string) => (buf: Oni.Buffer): boolean => { diff --git a/browser/src/Editor/NeovimEditor/HoverRenderer.tsx b/browser/src/Editor/NeovimEditor/HoverRenderer.tsx index 893acd13b9..15cf3e049b 100644 --- a/browser/src/Editor/NeovimEditor/HoverRenderer.tsx +++ b/browser/src/Editor/NeovimEditor/HoverRenderer.tsx @@ -8,9 +8,9 @@ import * as React from "react" import * as types from "vscode-languageserver-types" import getTokens from "./../../Services/SyntaxHighlighting/TokenGenerator" -import { enableMouse } from "./../../UI/components/common" +import styled, { enableMouse } from "./../../UI/components/common" import { ErrorInfo } from "./../../UI/components/ErrorInfo" -import { QuickInfoElement, QuickInfoWrapper } from "./../../UI/components/QuickInfo" +import { QuickInfoElement } from "./../../UI/components/QuickInfo" import QuickInfoWithTheme from "./../../UI/components/QuickInfoContainer" import * as Helpers from "./../../Plugins/Api/LanguageClient/LanguageClientHelpers" @@ -22,7 +22,9 @@ import { IToolTipsProvider } from "./ToolTipsProvider" const HoverToolTipId = "hover-tool-tip" -const HoverRendererContainer = QuickInfoWrapper.extend` +const HoverRendererContainer = styled.div` + user-select: none; + cursor: default; ${enableMouse}; ` diff --git a/browser/src/Editor/NeovimEditor/NeovimEditor.tsx b/browser/src/Editor/NeovimEditor/NeovimEditor.tsx index 348b848da7..383fb6b44c 100644 --- a/browser/src/Editor/NeovimEditor/NeovimEditor.tsx +++ b/browser/src/Editor/NeovimEditor/NeovimEditor.tsx @@ -69,7 +69,7 @@ import { Workspace } from "./../../Services/Workspace" import { Editor } from "./../Editor" -import { BufferManager, IBuffer } from "./../BufferManager" +import { BufferManager } from "./../BufferManager" import { CompletionMenu } from "./CompletionMenu" import { HoverRenderer } from "./HoverRenderer" import { NeovimPopupMenu } from "./NeovimPopupMenu" @@ -94,8 +94,6 @@ import CommandLine from "./../../UI/components/CommandLine" import ExternalMenus from "./../../UI/components/ExternalMenus" import WildMenu from "./../../UI/components/WildMenu" -import { WelcomeBufferLayer } from "./WelcomeBufferLayer" - import { CanvasRenderer } from "../../Renderer/CanvasRenderer" import { WebGLRenderer } from "../../Renderer/WebGL/WebGLRenderer" import { getInstance as getNotificationsInstance } from "./../../Services/Notifications" @@ -148,6 +146,7 @@ export class NeovimEditor extends Editor implements Oni.Editor { private _bufferLayerManager = getLayerManagerInstance() private _screenWithPredictions: ScreenWithPredictions + private _onShowWelcomeScreen = new Event() private _onNeovimQuit: Event = new Event() private _autoFocus: boolean = true @@ -156,6 +155,10 @@ export class NeovimEditor extends Editor implements Oni.Editor { return this._onNeovimQuit } + public get onShowWelcomeScreen() { + return this._onShowWelcomeScreen + } + public get /* override */ activeBuffer(): Oni.Buffer { return this._bufferManager.getBufferById(this._lastBufferId) } @@ -309,7 +312,7 @@ export class NeovimEditor extends Editor implements Oni.Editor { // Services const onColorsChanged = () => { - const updatedColors: any = this._colors.getColors() + const updatedColors = this._colors.getColors() this._actions.setColors(updatedColors) } @@ -834,6 +837,12 @@ export class NeovimEditor extends Editor implements Oni.Editor { this._neovimInstance.autoCommands.executeAutoCommand("FocusLost") } + public async createWelcomeBuffer() { + const buf = await this.openFile("WELCOME") + await buf.setScratchBuffer() + return buf + } + public async clearSelection(): Promise { await this._neovimInstance.input("") await this._neovimInstance.input("a") @@ -1034,8 +1043,7 @@ export class NeovimEditor extends Editor implements Oni.Editor { await this.openFiles(filesToOpen, { openMode: Oni.FileOpenMode.Edit }) } else { if (this._configuration.getValue("experimental.welcome.enabled")) { - const buf = await this.openFile("WELCOME") - buf.addLayer(new WelcomeBufferLayer()) + this._onShowWelcomeScreen.dispatch() } } @@ -1100,8 +1108,8 @@ export class NeovimEditor extends Editor implements Oni.Editor { { screen={this.props.screen} /> - + diff --git a/browser/src/Editor/NeovimEditor/WelcomeBufferLayer.tsx b/browser/src/Editor/NeovimEditor/WelcomeBufferLayer.tsx index 18cdc5d6f1..0b0b4e4030 100644 --- a/browser/src/Editor/NeovimEditor/WelcomeBufferLayer.tsx +++ b/browser/src/Editor/NeovimEditor/WelcomeBufferLayer.tsx @@ -4,39 +4,25 @@ * IEditor implementation for Neovim */ -import * as React from "react" - -import styled, { keyframes } from "styled-components" - -import { inputManager, InputManager } from "./../../Services/InputManager" - import * as Oni from "oni-api" +import * as Log from "oni-core-logging" +import { Event } from "oni-types" +import * as React from "react" -import { withProps } from "./../../UI/components/common" -import { VimNavigator } from "./../../UI/components/VimNavigator" - -const WelcomeWrapper = withProps<{}>(styled.div)` - background-color: ${p => p.theme["editor.background"]}; - color: ${p => p.theme["editor.foreground"]}; - - overflow-y: auto; - -webkit-user-select: none; - - width: 100%; - height: 100%; - opacity: 0; -` +import { getMetadata } from "./../../Services/Metadata" +import styled, { + Css, + css, + enableMouse, + getSelectedBorder, + keyframes, +} from "./../../UI/components/common" // const entrance = keyframes` // 0% { opacity: 0; transform: translateY(2px); } // 100% { opacity: 0.5; transform: translateY(0px); } // ` -const entranceFull = keyframes` - 0% { opacity: 0; transform: translateY(8px); } - 100% { opacity: 1; transform: translateY(0px); } -` - // const enterLeft = keyframes` // 0% { opacity: 0; transform: translateX(-4px); } // 100% { opacity: 1; transform: translateX(0px); } @@ -47,22 +33,53 @@ const entranceFull = keyframes` // 100% { opacity: 1; transform: translateX(0px); } // ` -const Column = styled.div` +const entranceFull = keyframes` + 0% { + opacity: 0; + transform: translateY(8px); + } + 100% { + opacity: 1; + transform: translateY(0px); + } +` +const WelcomeWrapper = styled.div` + background-color: ${p => p.theme["editor.background"]}; + color: ${p => p.theme["editor.foreground"]}; + overflow-y: hidden; + user-select: none; + pointer-events: all; + width: 100%; + height: 100%; + opacity: 0; + animation: ${entranceFull} 0.25s ease-in 0.1s forwards ${enableMouse}; +` +interface IColumnProps { + alignment?: string + flex?: string + height?: string + overflowY?: string +} + +const Column = styled("div")` + background: ${p => p.theme["editor.background"]}; display: flex; justify-content: center; - align-items: center; + align-items: ${({ alignment }) => alignment || "center"}; flex-direction: column; - width: 100%; - flex: 1 1 auto; + flex: ${({ flex }) => flex || "1 1 auto"}; + height: ${({ height }) => height || `auto`}; + ${({ overflowY }) => overflowY && `overflow-y: ${overflowY}`}; ` -const Row = styled.div` +const Row = styled<{ extension?: Css }, "div">("div")` display: flex; justify-content: center; align-items: center; flex-direction: row; opacity: 0; + ${({ extension }) => extension}; ` const TitleText = styled.div` @@ -96,38 +113,36 @@ const WelcomeButtonHoverStyled = ` box-shadow: 0 4px 8px 2px rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); ` -// box-shadow: 0 4px 8px 2px rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.1); - export interface WelcomeButtonWrapperProps { - selected: boolean + isSelected: boolean + borderSize: string } -const WelcomeButtonWrapper = withProps(styled.div)` +const WelcomeButtonWrapper = styled("button")` + box-sizing: border-box; + font-size: inherit; + font-family: inherit; border: 0px solid ${props => props.theme.foreground}; - border-left: ${props => - props.selected - ? "4px solid " + props.theme["highlight.mode.normal.background"] - : "4px solid transparent"}; + border-left: ${getSelectedBorder}; border-right: 4px solid transparent; - color: ${props => props.theme.foreground}; - background-color: ${props => props.theme.background}; - cursor: pointer; - + color: ${({ theme }) => theme.foreground}; + background-color: ${({ theme }) => theme.background}; + transform: ${({ isSelected }) => (isSelected ? "translateX(-4px)" : "translateX(0px)")}; transition: transform 0.25s; - transform: ${props => (props.selected ? "translateX(-4px)" : "translateX(0px)")}; - width: 100%; margin: 8px 0px; padding: 8px; - display: flex; flex-direction: row; - &:hover { - ${WelcomeButtonHoverStyled} + ${WelcomeButtonHoverStyled}; } +` +const AnimatedContainer = styled<{ duration: string }, "div">("div")` + width: 100%; + animation: ${entranceFull} ${p => p.duration} ease-in 1s both; ` const WelcomeButtonTitle = styled.span` @@ -141,7 +156,6 @@ const WelcomeButtonDescription = styled.span` font-size: 0.8em; opacity: 0.75; margin: 4px; - width: 100%; text-align: right; ` @@ -151,12 +165,30 @@ export interface WelcomeButtonProps { description: string command: string selected: boolean + onClick: () => void +} + +interface IChromeDiv extends HTMLButtonElement { + scrollIntoViewIfNeeded: () => void } -export class WelcomeButton extends React.PureComponent { - public render(): JSX.Element { +export class WelcomeButton extends React.PureComponent { + private _button = React.createRef() + + public componentDidUpdate(prevProps: WelcomeButtonProps) { + if (!prevProps.selected && this.props.selected) { + this._button.current.scrollIntoViewIfNeeded() + } + } + + public render() { return ( - + {this.props.title} {this.props.description} @@ -168,193 +200,304 @@ export interface WelcomeHeaderState { version: string } +export interface OniWithActiveSection extends Oni.Plugin.Api { + getActiveSection(): string +} + +type ExecuteCommand = (command: string, args?: T) => void + +export interface IWelcomeInputEvent { + direction: number + select: boolean +} + +interface ICommandMetadata { + command: string + args?: T +} + +export interface IWelcomeCommandsDictionary { + openFile: ICommandMetadata + openTutor: ICommandMetadata + openDocs: ICommandMetadata + openConfig: ICommandMetadata + openThemes: ICommandMetadata + openWorkspaceFolder: ICommandMetadata + commandPalette: ICommandMetadata + commandline: ICommandMetadata +} + export class WelcomeBufferLayer implements Oni.BufferLayer { - public get id(): string { + public inputEvent = new Event() + + public readonly welcomeCommands: IWelcomeCommandsDictionary = { + openFile: { + command: "oni.editor.newFile", + }, + openWorkspaceFolder: { + command: "workspace.openFolder", + }, + commandPalette: { + command: "quickOpen.show", + }, + commandline: { + command: "executeVimCommand", + }, + openTutor: { + command: "oni.tutor.open", + }, + openDocs: { + command: "oni.docs.open", + }, + openConfig: { + command: "oni.config.openUserConfig", + }, + openThemes: { + command: "oni.themes.open", + }, + } + + constructor(private _oni: OniWithActiveSection) {} + + public get id() { return "oni.welcome" } - public get friendlyName(): string { + public get friendlyName() { return "Welcome" } - public render(context: Oni.BufferLayerRenderContext): JSX.Element { + public isActive(): boolean { + const activeSection = this._oni.getActiveSection() + return activeSection === "editor" + } + + public handleInput(key: string) { + Log.info(`ONI WELCOME INPUT KEY: ${key}`) + switch (key) { + case "j": + this.inputEvent.dispatch({ direction: 1, select: false }) + break + case "k": + this.inputEvent.dispatch({ direction: -1, select: false }) + break + case "": + this.inputEvent.dispatch({ direction: 0, select: true }) + break + default: + this.inputEvent.dispatch({ direction: 0, select: false }) + } + } + + public executeCommand: ExecuteCommand = (cmd, args) => { + if (cmd) { + this._oni.commands.executeCommand(cmd, args) + } + } + + public render(context: Oni.BufferLayerRenderContext) { + const active = this._oni.getActiveSection() === "editor" + const ids = Object.values(this.welcomeCommands).map(({ command }) => command) return ( - - + + ) } } export interface WelcomeViewProps { - inputManager: InputManager + active: boolean + buttonIds: string[] + inputEvent: Event + commands: IWelcomeCommandsDictionary + executeCommand: ExecuteCommand } export interface WelcomeViewState { version: string + selectedId: string + currentIndex: number } -import { getMetadata } from "./../../Services/Metadata" +const buttonsRow = css` + width: 100%; + margin-top: 64px; + opacity: 1; +` -export const ButtonIds = [ - "oni.tutor.open", - "oni.docs.open", - "oni.configuration.open", - "oni.themes.open", - "workspace.newFile", - "workspace.openFolder", - "tasks.show", - "editor.openExCommands", -] +const titleRow = css` + width: 100%; + padding-top: 32px; + animation: ${entranceFull} 0.25s ease-in 0.25s forwards}; +` export class WelcomeView extends React.PureComponent { - constructor(props: WelcomeViewProps) { - super(props) + public state: WelcomeViewState = { + version: null, + currentIndex: 0, + selectedId: this.props.buttonIds[0], + } + + private _welcomeElement = React.createRef() + + public async componentDidMount() { + const metadata = await getMetadata() + this.setState({ version: metadata.version }) + this.props.inputEvent.subscribe(this.handleInput) + } + + public handleInput = ({ direction, select }: IWelcomeInputEvent) => { + const { currentIndex } = this.state - this.state = { - version: null, + const newIndex = this.getNextIndex(direction, currentIndex) + const selectedId = this.props.buttonIds[newIndex] + this.setState({ currentIndex: newIndex, selectedId }) + + if (select && this.props.active) { + const currentCommand = this.getCurrentCommand(selectedId) + this.props.executeCommand(currentCommand.command, currentCommand.args) } } - public componentDidMount(): void { - getMetadata().then(metadata => { - this.setState({ - version: metadata.version, - }) - }) + public getCurrentCommand(selectedId: string): ICommandMetadata { + const { commands } = this.props + const currentCommand = Object.values(commands).find(({ command }) => command === selectedId) + return currentCommand } - public render(): JSX.Element { - if (!this.state.version) { - return null + public getNextIndex(direction: number, currentIndex: number) { + const nextPosition = currentIndex + direction + switch (true) { + case nextPosition < 0: + return this.props.buttonIds.length - 1 + case nextPosition === this.props.buttonIds.length: + return 0 + default: + return nextPosition } + } - return ( - - + public componentDidUpdate() { + if (this.props.active && this._welcomeElement && this._welcomeElement.current) { + this._welcomeElement.current.focus() + } + } + + public render() { + const { version } = this.state + return version ? ( + + - + Oni Modern Modal Editing - + - - {"v" + this.state.version} + + {`v${this.state.version}`}
{"https://onivim.io"}
- + - ( - - )} +
- ) + ) : null } } -export interface IWelcomeBufferLayerCommandsViewProps { +export interface IWelcomeCommandsViewProps extends Partial { selectedId: string } -export class WelcomeBufferLayerCommandsView extends React.PureComponent< - IWelcomeBufferLayerCommandsViewProps, - {} -> { - public render(): JSX.Element { +export class WelcomeCommandsView extends React.PureComponent { + public render() { + const { commands, executeCommand } = this.props + const isSelected = (command: string) => command === this.props.selectedId return ( - -
- Learn - - -
-
- Customize - - -
-
+ + Quick Commands executeCommand(commands.openFile.command)} description="Control + N" - command="workspace.newFile" - selected={this.props.selectedId === "workspace.newFile"} + command={commands.openFile.command} + selected={isSelected(commands.openFile.command)} /> executeCommand(commands.openWorkspaceFolder.command)} description="Control + O" - command="workspace.openFolder" - selected={this.props.selectedId === "workspace.openFolder"} + command={commands.openWorkspaceFolder.command} + selected={isSelected(commands.openWorkspaceFolder.command)} /> executeCommand(commands.commandPalette.command)} description="Control + Shift + P" - command="tasks.show" - selected={this.props.selectedId === "tasks.show"} + command={commands.commandPalette.command} + selected={isSelected(commands.commandPalette.command)} /> executeCommand(commands.commandline.command)} + selected={isSelected(commands.commandline.command)} + /> + + + Learn + executeCommand(commands.openTutor.command)} + description="Learn modal editing with an interactive tutorial." + command={commands.openTutor.command} + selected={isSelected(commands.openTutor.command)} + /> + executeCommand(commands.openDocs.command)} + description="Discover what Oni can do for you." + command={commands.openDocs.command} + selected={isSelected(commands.openDocs.command)} + /> + + + Customize + executeCommand(commands.openConfig.command)} + description="Make Oni work the way you want." + command={commands.openConfig.command} + selected={isSelected(commands.openConfig.command)} + /> + executeCommand(commands.openThemes.command)} + description="Choose a theme that works for you." + command={commands.openThemes.command} + selected={isSelected(commands.openThemes.command)} /> -
+
) } diff --git a/browser/src/Editor/OniEditor/OniEditor.tsx b/browser/src/Editor/OniEditor/OniEditor.tsx index 6f711b5d2d..403d2c3df4 100644 --- a/browser/src/Editor/OniEditor/OniEditor.tsx +++ b/browser/src/Editor/OniEditor/OniEditor.tsx @@ -51,6 +51,7 @@ import { SplitDirection, windowManager } from "./../../Services/WindowManager" import { ISession } from "../../Services/Sessions" import { IBuffer } from "../BufferManager" +import { OniWithActiveSection, WelcomeBufferLayer } from "../NeovimEditor/WelcomeBufferLayer" import ColorHighlightLayer from "./ColorHighlightLayer" import { ImageBufferLayer } from "./ImageBufferLayer" import IndentLineBufferLayer from "./IndentGuideBufferLayer" @@ -208,6 +209,13 @@ export class OniEditor extends Utility.Disposable implements Oni.Editor { _buf => new ColorHighlightLayer(this._configuration), ) } + + this._neovimEditor.onShowWelcomeScreen.subscribe(async () => { + const oni = this._pluginManager.getApi() + const welcomeBuffer = await this._neovimEditor.createWelcomeBuffer() + const welcomeLayer = new WelcomeBufferLayer(oni as OniWithActiveSection) + welcomeBuffer.addLayer(welcomeLayer) + }) } public dispose(): void { diff --git a/browser/src/Plugins/Api/Oni.ts b/browser/src/Plugins/Api/Oni.ts index 8f43d40583..5314c05a96 100644 --- a/browser/src/Plugins/Api/Oni.ts +++ b/browser/src/Plugins/Api/Oni.ts @@ -184,7 +184,7 @@ export class Oni implements OniApi.Plugin.Api { return getWorkspaceInstance() } - public get helpers(): any { + public get helpers() { return helpers } @@ -198,6 +198,25 @@ export class Oni implements OniApi.Plugin.Api { this._services = new Services() } + public getActiveSection() { + const isInsertOrCommandMode = () => { + return ( + this.editors.activeEditor.mode === "insert" || + this.editors.activeEditor.mode === "cmdline_normal" + ) + } + switch (true) { + case this.menu.isMenuOpen(): + return "menu" + case this.sidebar && this.sidebar.isFocused: + return this.sidebar.activeEntryId + case isInsertOrCommandMode(): + return "commandline" + default: + return "editor" + } + } + public populateQuickFix(entries: OniApi.QuickFixEntry[]): void { const neovim: any = editorManager.activeEditor.neovim neovim.quickFix.setqflist(entries, "Search Results") diff --git a/browser/src/Services/Browser/index.tsx b/browser/src/Services/Browser/index.tsx index 0cf849ea18..97b96c81b2 100644 --- a/browser/src/Services/Browser/index.tsx +++ b/browser/src/Services/Browser/index.tsx @@ -173,6 +173,13 @@ export const activate = ( detail: null, }) + commandManager.registerCommand({ + command: "oni.docs.open", + execute: () => openUrl("https://onivim.github.io/oni-docs/#/"), + name: "Browser: Open Documentation", + detail: "Open Oni's Documentation website", + }) + const getLayerForBuffer = (buffer: Oni.Buffer): BrowserLayer => { return (buffer as IBuffer).getLayerById("oni.browser") } diff --git a/browser/src/Services/Commands/GlobalCommands.ts b/browser/src/Services/Commands/GlobalCommands.ts index 05d6c75cb9..a568fa05be 100644 --- a/browser/src/Services/Commands/GlobalCommands.ts +++ b/browser/src/Services/Commands/GlobalCommands.ts @@ -47,7 +47,7 @@ export const activate = ( const commands = [ new CallbackCommand("editor.executeVimCommand", null, null, (message: string) => { - const neovim = editorManager.activeEditor.neovim + const { neovim } = editorManager.activeEditor if (message.startsWith(":")) { neovim.command('exec "' + message + '"') } else { @@ -60,16 +60,20 @@ export const activate = ( multiProcess.openNewWindow(), ), + new CallbackCommand("oni.editor.newFile", "Oni: Create new file", "Create a new file", () => + editorManager.activeEditor.neovim.command("enew!"), + ), + new CallbackCommand( "oni.editor.maximize", - "Maximize Window", + "Oni: Maximize Window", "Maximize the current window", () => remote.getCurrentWindow().maximize(), ), new CallbackCommand( "oni.editor.minimize", - "Minimize Window", + "Oni: Minimize Window", "Minimize the current window", () => remote.getCurrentWindow().minimize(), ), @@ -80,13 +84,13 @@ export const activate = ( new CallbackCommand( "oni.process.cycleNext", - "Focus Next Oni", + "Oni: Focus Next Oni", "Switch to the next running instance of Oni", () => multiProcess.focusNextInstance(), ), new CallbackCommand( "oni.process.cyclePrevious", - "Focus Previous Oni", + "Oni: Focus Previous Oni", "Switch to the previous running instance of Oni", () => multiProcess.focusPreviousInstance(), ), @@ -114,7 +118,7 @@ export const activate = ( if (Platform.isMac()) { const addToPathCommand = new CallbackCommand( "oni.editor.removeFromPath", - "Remove from PATH", + "Oni: Remove from PATH", "Disable executing 'oni' from terminal", Platform.removeFromPath, () => Platform.isAddedToPath(), @@ -123,7 +127,7 @@ export const activate = ( const removeFromPathCommand = new CallbackCommand( "oni.editor.addToPath", - "Add to PATH", + "Oni: Add to PATH", "Enable executing 'oni' from terminal", Platform.addToPath, () => !Platform.isAddedToPath(), diff --git a/browser/src/Services/VersionControl/VersionControlManager.tsx b/browser/src/Services/VersionControl/VersionControlManager.tsx index 34ecd8924e..1e7d2eb086 100644 --- a/browser/src/Services/VersionControl/VersionControlManager.tsx +++ b/browser/src/Services/VersionControl/VersionControlManager.tsx @@ -141,9 +141,11 @@ export class VersionControlManager { ) this._sidebar.add("code-fork", vcsPane) // TODO: Refactor API } - + // TODO: this should only be active if this is a file under version control this._bufferLayerManager.addBufferLayer( - () => this._oni.configuration.getValue("experimental.vcs.blame.enabled"), + buffer => + this._oni.configuration.getValue("experimental.vcs.blame.enabled") && + !!buffer.filePath, buf => new VersionControlBlameLayer( buf, diff --git a/browser/src/UI/components/common.ts b/browser/src/UI/components/common.ts index 68eff28003..da1eeaeae7 100644 --- a/browser/src/UI/components/common.ts +++ b/browser/src/UI/components/common.ts @@ -77,11 +77,21 @@ export const StackLayer = styled<{ zIndex?: number | string }, "div">("div")` ${p => p.zIndex && `z-index: ${p.zIndex}`}; ` +type GetBorder = ( + args: { + isSelected?: boolean + borderSize?: string + theme?: styledComponents.ThemeProps + }, +) => string + +export const getSelectedBorder: GetBorder = ({ isSelected, borderSize = "1px", theme }) => + isSelected + ? `${borderSize} solid ${theme["highlight.mode.normal.background"]}` + : `${borderSize} solid transparent` + export const sidebarItemSelected = css` - border: ${(p: { isSelected?: boolean; theme?: styledComponents.ThemeProps }) => - p.isSelected - ? `1px solid ${p.theme["highlight.mode.normal.background"]}` - : `1px solid transparent`}; + border: ${getSelectedBorder}; ` export type StyledFunction = styledComponents.ThemedStyledFunction diff --git a/browser/test/Editor/NeovimEditor/BufferManagerTest.ts b/browser/test/Editor/NeovimEditor/BufferManagerTest.ts deleted file mode 100644 index cf2e72684e..0000000000 --- a/browser/test/Editor/NeovimEditor/BufferManagerTest.ts +++ /dev/null @@ -1,80 +0,0 @@ -import * as assert from "assert" - -import { BufferManager, InactiveBuffer } from "./../../../src/Editor/BufferManager" - -describe("Buffer Manager Tests", () => { - const neovim = {} as any - const actions = {} as any - const store = {} as any - const manager = new BufferManager(neovim, actions, store) - const event = { - bufferFullPath: "/test/file", - bufferTotalLines: 2, - bufferNumber: 1, - modified: false, - hidden: false, - listed: true, - version: 1, - line: 0, - column: 0, - byte: 8, - filetype: "js", - tabNumber: 1, - windowNumber: 1, - wincol: 10, - winline: 25, - windowTopLine: 0, - windowBottomLine: 200, - windowWidth: 100, - windowHeight: 100, - tabstop: 8, - shiftwidth: 2, - comments: "://,ex:*/", - } - - const inactive1 = { - bufferNumber: 2, - bufferFullPath: "/test/two", - filetype: "js", - buftype: "", - modified: false, - hidden: false, - listed: true, - version: 1, - } - - it("Should correctly set buffer variables", () => { - manager.updateBufferFromEvent(event) - const buffer = manager.getBufferById("1") - assert(buffer.tabstop === 8, "tabstop is set correctly") - assert(buffer.shiftwidth === 2, "shiftwidth is set correctly") - assert(buffer.comment.defaults.includes("//"), "comments are set correctly") - assert(buffer.comment.end.includes("*/"), "comments are set correctly") - }) - - it("Should correctly populate the buffer list", () => { - manager.updateBufferFromEvent(event) - manager.populateBufferList({ - current: event, - existingBuffers: [inactive1], - }) - - const buffers = manager.getBuffers() - assert(buffers.length === 2, "Two buffers were added") - assert( - buffers.find(buffer => buffer instanceof InactiveBuffer), - "One of the buffers is an inactive buffer", - ) - }) - - it("Should correctly format a comment string (based on neovim &comment option)", () => { - manager.updateBufferFromEvent(event) - const buffer = manager.getBufferById("1") - const comment = "s1:/*,ex:*/,://,b:#,:%" - const formatted = buffer.formatCommentOption(comment) - assert(formatted.start.includes("/*"), "Correctly parses a comment string") - assert(formatted.end.includes("*/"), "Correctly parses a comment string") - assert(formatted.defaults.includes("//"), "Correctly parses a comment string") - assert(formatted.defaults.includes("#"), "Correctly parses a comment string") - }) -}) diff --git a/package.json b/package.json index e7dc916f50..e66300f5b5 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,14 @@ "homepage": "https://www.onivim.io", "version": "0.3.7", "description": "Code editor with a modern twist on modal editing - powered by neovim.", - "keywords": ["vim", "neovim", "text", "editor", "ide", "vim"], + "keywords": [ + "vim", + "neovim", + "text", + "editor", + "ide", + "vim" + ], "main": "./lib/main/src/main.js", "bin": { "oni": "./lib/cli/src/cli.js", @@ -43,23 +50,39 @@ "mac": { "artifactName": "${productName}-${version}-osx.${ext}", "category": "public.app-category.developer-tools", - "target": ["dmg"], - "files": ["bin/osx/**/*"] + "target": [ + "dmg" + ], + "files": [ + "bin/osx/**/*" + ] }, "linux": { "artifactName": "${productName}-${version}-${arch}-linux.${ext}", "maintainer": "bryphe@outlook.com", - "target": ["tar.gz", "deb", "rpm"] + "target": [ + "tar.gz", + "deb", + "rpm" + ] }, "win": { - "target": ["zip", "dir"], - "files": ["bin/x86/**/*"] + "target": [ + "zip", + "dir" + ], + "files": [ + "bin/x86/**/*" + ] }, "fileAssociations": [ { "name": "ADA source", "role": "Editor", - "ext": ["adb", "ads"] + "ext": [ + "adb", + "ads" + ] }, { "name": "Compiled AppleScript", @@ -79,12 +102,20 @@ { "name": "ASP document", "role": "Editor", - "ext": ["asp", "asa"] + "ext": [ + "asp", + "asa" + ] }, { "name": "ASP.NET document", "role": "Editor", - "ext": ["aspx", "ascx", "asmx", "ashx"] + "ext": [ + "aspx", + "ascx", + "asmx", + "ashx" + ] }, { "name": "BibTeX bibliography", @@ -99,7 +130,13 @@ { "name": "C++ source", "role": "Editor", - "ext": ["cc", "cp", "cpp", "cxx", "c++"] + "ext": [ + "cc", + "cp", + "cpp", + "cxx", + "c++" + ] }, { "name": "C# source", @@ -124,7 +161,10 @@ { "name": "Clojure source", "role": "Editor", - "ext": ["clj", "cljs"] + "ext": [ + "clj", + "cljs" + ] }, { "name": "Comma separated values", @@ -139,12 +179,20 @@ { "name": "CGI script", "role": "Editor", - "ext": ["cgi", "fcgi"] + "ext": [ + "cgi", + "fcgi" + ] }, { "name": "Configuration file", "role": "Editor", - "ext": ["cfg", "conf", "config", "htaccess"] + "ext": [ + "cfg", + "conf", + "config", + "htaccess" + ] }, { "name": "Cascading style sheet", @@ -169,7 +217,10 @@ { "name": "Erlang source", "role": "Editor", - "ext": ["erl", "hrl"] + "ext": [ + "erl", + "hrl" + ] }, { "name": "F-Script source", @@ -179,17 +230,32 @@ { "name": "Fortran source", "role": "Editor", - "ext": ["f", "for", "fpp", "f77", "f90", "f95"] + "ext": [ + "f", + "for", + "fpp", + "f77", + "f90", + "f95" + ] }, { "name": "Header", "role": "Editor", - "ext": ["h", "pch"] + "ext": [ + "h", + "pch" + ] }, { "name": "C++ header", "role": "Editor", - "ext": ["hh", "hpp", "hxx", "h++"] + "ext": [ + "hh", + "hpp", + "hxx", + "h++" + ] }, { "name": "Go source", @@ -199,17 +265,28 @@ { "name": "GTD document", "role": "Editor", - "ext": ["gtd", "gtdlog"] + "ext": [ + "gtd", + "gtdlog" + ] }, { "name": "Haskell source", "role": "Editor", - "ext": ["hs", "lhs"] + "ext": [ + "hs", + "lhs" + ] }, { "name": "HTML document", "role": "Editor", - "ext": ["htm", "html", "phtml", "shtml"] + "ext": [ + "htm", + "html", + "phtml", + "shtml" + ] }, { "name": "Include file", @@ -249,7 +326,10 @@ { "name": "JavaScript source", "role": "Editor", - "ext": ["js", "htc"] + "ext": [ + "js", + "htc" + ] }, { "name": "Java Server Page", @@ -274,7 +354,14 @@ { "name": "Lisp source", "role": "Editor", - "ext": ["lisp", "cl", "l", "lsp", "mud", "el"] + "ext": [ + "lisp", + "cl", + "l", + "lsp", + "mud", + "el" + ] }, { "name": "Log file", @@ -294,7 +381,12 @@ { "name": "Markdown document", "role": "Editor", - "ext": ["markdown", "mdown", "markdn", "md"] + "ext": [ + "markdown", + "mdown", + "markdn", + "md" + ] }, { "name": "Makefile source", @@ -304,17 +396,29 @@ { "name": "Mediawiki document", "role": "Editor", - "ext": ["wiki", "wikipedia", "mediawiki"] + "ext": [ + "wiki", + "wikipedia", + "mediawiki" + ] }, { "name": "MIPS assembler source", "role": "Editor", - "ext": ["s", "mips", "spim", "asm"] + "ext": [ + "s", + "mips", + "spim", + "asm" + ] }, { "name": "Modula-3 source", "role": "Editor", - "ext": ["m3", "cm3"] + "ext": [ + "m3", + "cm3" + ] }, { "name": "MoinMoin document", @@ -334,17 +438,28 @@ { "name": "OCaml source", "role": "Editor", - "ext": ["ml", "mli", "mll", "mly"] + "ext": [ + "ml", + "mli", + "mll", + "mly" + ] }, { "name": "Mustache document", "role": "Editor", - "ext": ["mustache", "hbs"] + "ext": [ + "mustache", + "hbs" + ] }, { "name": "Pascal source", "role": "Editor", - "ext": ["pas", "p"] + "ext": [ + "pas", + "p" + ] }, { "name": "Patch file", @@ -354,7 +469,11 @@ { "name": "Perl source", "role": "Editor", - "ext": ["pl", "pod", "perl"] + "ext": [ + "pl", + "pod", + "perl" + ] }, { "name": "Perl module", @@ -364,47 +483,80 @@ { "name": "PHP source", "role": "Editor", - "ext": ["php", "php3", "php4", "php5"] + "ext": [ + "php", + "php3", + "php4", + "php5" + ] }, { "name": "PostScript source", "role": "Editor", - "ext": ["ps", "eps"] + "ext": [ + "ps", + "eps" + ] }, { "name": "Property list", "role": "Editor", - "ext": ["dict", "plist", "scriptSuite", "scriptTerminology"] + "ext": [ + "dict", + "plist", + "scriptSuite", + "scriptTerminology" + ] }, { "name": "Python source", "role": "Editor", - "ext": ["py", "rpy", "cpy", "python"] + "ext": [ + "py", + "rpy", + "cpy", + "python" + ] }, { "name": "R source", "role": "Editor", - "ext": ["r", "s"] + "ext": [ + "r", + "s" + ] }, { "name": "Ragel source", "role": "Editor", - "ext": ["rl", "ragel"] + "ext": [ + "rl", + "ragel" + ] }, { "name": "Remind document", "role": "Editor", - "ext": ["rem", "remind"] + "ext": [ + "rem", + "remind" + ] }, { "name": "reStructuredText document", "role": "Editor", - "ext": ["rst", "rest"] + "ext": [ + "rst", + "rest" + ] }, { "name": "HTML with embedded Ruby", "role": "Editor", - "ext": ["rhtml", "erb"] + "ext": [ + "rhtml", + "erb" + ] }, { "name": "SQL with embedded Ruby", @@ -414,17 +566,28 @@ { "name": "Ruby source", "role": "Editor", - "ext": ["rb", "rbx", "rjs", "rxml"] + "ext": [ + "rb", + "rbx", + "rjs", + "rxml" + ] }, { "name": "Sass source", "role": "Editor", - "ext": ["sass", "scss"] + "ext": [ + "sass", + "scss" + ] }, { "name": "Scheme source", "role": "Editor", - "ext": ["scm", "sch"] + "ext": [ + "scm", + "sch" + ] }, { "name": "Setext document", @@ -472,7 +635,10 @@ { "name": "SWIG source", "role": "Editor", - "ext": ["i", "swg"] + "ext": [ + "i", + "swg" + ] }, { "name": "Tcl source", @@ -482,12 +648,20 @@ { "name": "TeX document", "role": "Editor", - "ext": ["tex", "sty", "cls"] + "ext": [ + "tex", + "sty", + "cls" + ] }, { "name": "Plain text document", "role": "Editor", - "ext": ["text", "txt", "utf8"] + "ext": [ + "text", + "txt", + "utf8" + ] }, { "name": "Textile document", @@ -507,17 +681,32 @@ { "name": "XML document", "role": "Editor", - "ext": ["xml", "xsd", "xib", "rss", "tld", "pt", "cpt", "dtml"] + "ext": [ + "xml", + "xsd", + "xib", + "rss", + "tld", + "pt", + "cpt", + "dtml" + ] }, { "name": "XSL stylesheet", "role": "Editor", - "ext": ["xsl", "xslt"] + "ext": [ + "xsl", + "xslt" + ] }, { "name": "Electronic business card", "role": "Editor", - "ext": ["vcf", "vcard"] + "ext": [ + "vcf", + "vcard" + ] }, { "name": "Visual Basic source", @@ -527,7 +716,10 @@ { "name": "YAML document", "role": "Editor", - "ext": ["yaml", "yml"] + "ext": [ + "yaml", + "yml" + ] }, { "name": "Text document", @@ -589,10 +781,8 @@ "scripts": { "precommit": "pretty-quick --staged", "prepush": "yarn run build && yarn run lint", - "build": - "yarn run build:browser && yarn run build:webview_preload && yarn run build:main && yarn run build:plugins && yarn run build:cli", - "build-debug": - "yarn run build:browser-debug && yarn run build:main && yarn run build:plugins", + "build": "yarn run build:browser && yarn run build:webview_preload && yarn run build:main && yarn run build:plugins && yarn run build:cli", + "build-debug": "yarn run build:browser-debug && yarn run build:main && yarn run build:plugins", "build:browser": "webpack --config browser/webpack.production.config.js", "build:browser-debug": "webpack --config browser/webpack.debug.config.js", "build:main": "cd main && tsc -p tsconfig.json", @@ -600,92 +790,65 @@ "build:cli": "cd cli && tsc -p tsconfig.json", "build:plugin:oni-plugin-typescript": "cd vim/core/oni-plugin-typescript && yarn run build", "build:plugin:oni-plugin-git": "cd vim/core/oni-plugin-git && yarn run build", - "build:plugin:oni-plugin-markdown-preview": - "cd extensions/oni-plugin-markdown-preview && yarn run build", + "build:plugin:oni-plugin-markdown-preview": "cd extensions/oni-plugin-markdown-preview && yarn run build", "build:plugin:oni-plugin-quickopen": "cd extensions/oni-plugin-quickopen && yarn run build", "build:test": "yarn run build:test:unit && yarn run build:test:integration", "build:test:integration": "cd test && tsc -p tsconfig.json", "build:test:unit": "run-s build:test:unit:*", - "build:test:unit:browser": - "rimraf lib_test/browser && cd browser && tsc -p tsconfig.test.json", + "build:test:unit:browser": "rimraf lib_test/browser && cd browser && tsc -p tsconfig.test.json", "build:test:unit:main": "rimraf lib_test/main && cd main && tsc -p tsconfig.test.json", "build:webview_preload": "cd webview_preload && tsc -p tsconfig.json", "check-cached-binaries": "node build/script/CheckBinariesForBuild.js", "copy-icons": "node build/CopyIcons.js", - "debug:test:unit:browser": - "cd browser && tsc -p tsconfig.test.json && electron-mocha --interactive --debug --renderer --require testHelpers.js --recursive ../lib_test/browser/test", - "demo:screenshot": - "yarn run build:test && cross-env DEMO_TEST=HeroScreenshot mocha -t 30000000 lib_test/test/Demo.js", - "demo:video": - "yarn run build:test && cross-env DEMO_TEST=HeroDemo mocha -t 30000000 lib_test/test/Demo.js", - "dist:win:x86": - "cross-env ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES=1 build --arch ia32 --publish never", - "dist:win:x64": - "cross-env ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES=1 build --arch x64 --publish never", - "pack:win": - "node build/BuildSetupTemplate.js && innosetup-compiler dist/setup.iss --verbose --O=dist", + "debug:test:unit:browser": "cd browser && tsc -p tsconfig.test.json && electron-mocha --interactive --debug --renderer --require testHelpers.js --recursive ../lib_test/browser/test", + "demo:screenshot": "yarn run build:test && cross-env DEMO_TEST=HeroScreenshot mocha -t 30000000 lib_test/test/Demo.js", + "demo:video": "yarn run build:test && cross-env DEMO_TEST=HeroDemo mocha -t 30000000 lib_test/test/Demo.js", + "dist:win:x86": "cross-env ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES=1 build --arch ia32 --publish never", + "dist:win:x64": "cross-env ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES=1 build --arch x64 --publish never", + "pack:win": "node build/BuildSetupTemplate.js && innosetup-compiler dist/setup.iss --verbose --O=dist", "test": "yarn run test:unit && yarn run test:integration", - "test:setup": - "yarn run build:test:integration && mocha -t 120000 --recursive lib_test/test/setup", - "test:integration": - "yarn run build:test:integration && mocha -t 120000 lib_test/test/CiTests.js --bail", + "test:setup": "yarn run build:test:integration && mocha -t 120000 --recursive lib_test/test/setup", + "test:integration": "yarn run build:test:integration && mocha -t 120000 lib_test/test/CiTests.js --bail", "test:unit:react": "jest --config ./jest.config.js ./ui-tests", "test:unit:react:watch": "jest --config ./jest.config.js ./ui-tests --watch", "test:unit:react:coverage": "jest --config ./jest.config.js ./ui-tests --coverage", - "test:unit:browser": - "yarn run build:test:unit:browser && cd browser && electron-mocha --renderer --require testHelpers.js --recursive ../lib_test/browser/test", - "test:unit": - "yarn run test:unit:browser && yarn run test:unit:main && yarn run test:unit:react", - "test:unit:main": - "yarn run build:test:unit:main && cd main && electron-mocha --renderer --recursive ../lib_test/main/test", + "test:unit:browser": "yarn run build:test:unit:browser && cd browser && electron-mocha --renderer --require testHelpers.js --recursive ../lib_test/browser/test", + "test:unit": "yarn run test:unit:browser && yarn run test:unit:main && yarn run test:unit:react", + "test:unit:main": "yarn run build:test:unit:main && cd main && electron-mocha --renderer --recursive ../lib_test/main/test", "upload:dist": "node build/script/UploadDistributionBuildsToAzure", "fix-lint": "run-p fix-lint:*", - "fix-lint:browser": - "tslint --fix --project browser/tsconfig.json --exclude **/node_modules/**/* --config tslint.json && tslint --fix --project vim/core/oni-plugin-typescript/tsconfig.json --config tslint.json && tslint --fix --project extensions/oni-plugin-markdown-preview/tsconfig.json --config tslint.json", + "fix-lint:browser": "tslint --fix --project browser/tsconfig.json --exclude **/node_modules/**/* --config tslint.json && tslint --fix --project vim/core/oni-plugin-typescript/tsconfig.json --config tslint.json && tslint --fix --project extensions/oni-plugin-markdown-preview/tsconfig.json --config tslint.json", "fix-lint:cli": "tslint --fix --project cli/tsconfig.json --config tslint.json", "fix-lint:main": "tslint --fix --project main/tsconfig.json --config tslint.json", "fix-lint:test": "tslint --fix --project test/tsconfig.json --config tslint.json", "lint": "yarn run lint:browser && yarn run lint:main && yarn run lint:test", - "lint:browser": - "tslint --project browser/tsconfig.json --config tslint.json && tslint --project vim/core/oni-plugin-typescript/tsconfig.json --config tslint.json && tslint --project extensions/oni-plugin-markdown-preview/tsconfig.json --config tslint.json", + "lint:browser": "tslint --project browser/tsconfig.json --config tslint.json && tslint --project vim/core/oni-plugin-typescript/tsconfig.json --config tslint.json && tslint --project extensions/oni-plugin-markdown-preview/tsconfig.json --config tslint.json", "lint:cli": "tslint --project cli/tsconfig.json --config tslint.json", "lint:main": "tslint --project main/tsconfig.json --config tslint.json", - "lint:test": - "tslint --project test/tsconfig.json --config tslint.json && tslint vim/core/oni-plugin-typescript/src/**/*.ts && tslint extensions/oni-plugin-markdown-preview/src/**/*.ts", + "lint:test": "tslint --project test/tsconfig.json --config tslint.json && tslint vim/core/oni-plugin-typescript/src/**/*.ts && tslint extensions/oni-plugin-markdown-preview/src/**/*.ts", "pack": "cross-env ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES=1 build --publish never", - "ccov:instrument": - "nyc instrument --all true --sourceMap false lib_test/browser/src lib_test/browser/src_ccov", - "ccov:test:browser": - "cross-env ONI_CCOV=1 electron-mocha --renderer --require browser/testHelpers.js -R browser/testCoverageReporter --recursive lib_test/browser/test", - "ccov:remap:browser:html": - "cd lib_test/browser/src && remap-istanbul --input ../../../coverage/coverage-final.json --output html-report --type html", - "ccov:remap:browser:lcov": - "cd lib_test/browser/src && remap-istanbul --input ../../../coverage/coverage-final.json --output lcov.info --type lcovonly", + "ccov:instrument": "nyc instrument --all true --sourceMap false lib_test/browser/src lib_test/browser/src_ccov", + "ccov:test:browser": "cross-env ONI_CCOV=1 electron-mocha --renderer --require browser/testHelpers.js -R browser/testCoverageReporter --recursive lib_test/browser/test", + "ccov:remap:browser:html": "cd lib_test/browser/src && remap-istanbul --input ../../../coverage/coverage-final.json --output html-report --type html", + "ccov:remap:browser:lcov": "cd lib_test/browser/src && remap-istanbul --input ../../../coverage/coverage-final.json --output lcov.info --type lcovonly", "ccov:clean": "rimraf coverage", "ccov:upload:jest": "cd ./coverage/jest && codecov", "ccov:upload": "codecov", "launch": "electron lib/main/src/main.js", - "start": - "concurrently --kill-others \"yarn run start-hot\" \"yarn run watch:browser\" \"yarn run watch:plugins\"", - "start-hot": - "cross-env ONI_WEBPACK_LOAD=1 NODE_ENV=development electron lib/main/src/main.js", + "start": "concurrently --kill-others \"yarn run start-hot\" \"yarn run watch:browser\" \"yarn run watch:plugins\"", + "start-hot": "cross-env ONI_WEBPACK_LOAD=1 NODE_ENV=development electron lib/main/src/main.js", "start-not-dev": "cross-env electron main.js", - "watch:browser": - "webpack-dev-server --config browser/webpack.development.config.js --host localhost --port 8191", + "watch:browser": "webpack-dev-server --config browser/webpack.development.config.js --host localhost --port 8191", "watch:plugins": "run-p --race watch:plugins:*", "watch:plugins:oni-plugin-typescript": "cd vim/core/oni-plugin-typescript && tsc --watch", - "watch:plugins:oni-plugin-markdown-preview": - "cd extensions/oni-plugin-markdown-preview && tsc --watch", + "watch:plugins:oni-plugin-markdown-preview": "cd extensions/oni-plugin-markdown-preview && tsc --watch", "watch:plugins:oni-plugin-git": "cd vim/core/oni-plugin-git && tsc --watch", "install:plugins": "run-s install:plugins:*", - "install:plugins:oni-plugin-markdown-preview": - "cd extensions/oni-plugin-markdown-preview && yarn install --prod", - "install:plugins:oni-plugin-prettier": - "cd extensions/oni-plugin-prettier && yarn install --prod", + "install:plugins:oni-plugin-markdown-preview": "cd extensions/oni-plugin-markdown-preview && yarn install --prod", + "install:plugins:oni-plugin-prettier": "cd extensions/oni-plugin-prettier && yarn install --prod", "install:plugins:oni-plugin-git": "cd vim/core/oni-plugin-git && yarn install --prod", "postinstall": "yarn run install:plugins && electron-rebuild && opencollective postinstall", - "profile:webpack": - "webpack --config browser/webpack.production.config.js --profile --json > stats.json && webpack-bundle-analyzer browser/stats.json" + "profile:webpack": "webpack --config browser/webpack.production.config.js --profile --json > stats.json && webpack-bundle-analyzer browser/stats.json" }, "repository": { "type": "git", @@ -718,7 +881,7 @@ "shell-env": "^0.3.0", "shelljs": "0.7.7", "simple-git": "^1.92.0", - "styled-components": "^3.2.6", + "styled-components": "^3.4.4", "typescript": "^2.8.1", "vscode-css-languageserver-bin": "^1.2.1", "vscode-html-languageserver-bin": "^1.1.0", diff --git a/test/CiTests.ts b/test/CiTests.ts index 4c2904646e..72d27b793c 100644 --- a/test/CiTests.ts +++ b/test/CiTests.ts @@ -24,6 +24,8 @@ const CiTests = [ "Configuration.TypeScriptEditor.NewConfigurationTest", "Configuration.TypeScriptEditor.CompletionTest", + "Welcome.BufferLayerTest", + "TabBarSneakTest", "initVimPromptNotificationTest", "Editor.BuffersCursorTest", diff --git a/test/ci/Welcome.BufferLayerTest.ts b/test/ci/Welcome.BufferLayerTest.ts new file mode 100644 index 0000000000..aba03b7094 --- /dev/null +++ b/test/ci/Welcome.BufferLayerTest.ts @@ -0,0 +1,38 @@ +/** + * Test script for the Welcome Screen Buffer Layer. + */ + +import * as assert from "assert" +import * as os from "os" +import * as path from "path" + +import * as Oni from "oni-api" +import { getSingleElementBySelector } from "./Common" + +export const test = async (oni: Oni.Plugin.Api) => { + await oni.automation.waitForEditors() + await oni.automation.sleep(2000) + + const element = getSingleElementBySelector("[data-id='welcome-screen']") + + assert.ok(!!element, "Validate the welcome screen is present") + + assert.ok( + oni.editors.activeEditor.activeBuffer.filePath.includes("WELCOME"), + "Validate that the current buffer is the Welcome one", + ) + oni.automation.sendKeys("") + assert.ok( + !!oni.editors.activeEditor.activeBuffer.filePath, + "Validate it opens empty unnamed new file", + ) +} + +export const settings = { + config: { + "oni.useDefaultConfig": true, + "oni.loadInitVim": false, + "experimental.welcome.enabled": true, + "_internal.hasCheckedInitVim": true, + }, +} diff --git a/ui-tests/BufferManager.test.ts b/ui-tests/BufferManager.test.ts new file mode 100644 index 0000000000..efc7ae079b --- /dev/null +++ b/ui-tests/BufferManager.test.ts @@ -0,0 +1,129 @@ +import { BufferManager, InactiveBuffer } from "./../browser/src/Editor/BufferManager" + +describe("Buffer Manager Tests", () => { + const neovim = {} as any + const actions = {} as any + const store = { + getState: jest.fn(), + } as any + + const manager = new BufferManager(neovim, actions, store) + + const event = { + bufferFullPath: "/test/file", + bufferTotalLines: 2, + bufferNumber: 1, + modified: false, + hidden: false, + listed: true, + version: 1, + line: 0, + column: 0, + byte: 8, + filetype: "js", + tabNumber: 1, + windowNumber: 1, + wincol: 10, + winline: 25, + windowTopLine: 0, + windowBottomLine: 200, + windowWidth: 100, + windowHeight: 100, + tabstop: 8, + shiftwidth: 2, + comments: "://,ex:*/", + } + + const inactive1 = { + bufferNumber: 2, + bufferFullPath: "/test/two", + filetype: "js", + buftype: "", + modified: false, + hidden: false, + listed: true, + version: 1, + } + + beforeEach(() => { + manager.updateBufferFromEvent(event) + }) + + it("Should correctly set buffer variables", () => { + const buffer = manager.getBufferById("1") + expect(buffer.tabstop).toBe(8) + expect(buffer.shiftwidth).toBe(2) + expect(buffer.comment.defaults.includes("//")).toBe(true) + expect(buffer.comment.end.includes("*/")).toBe(true) + }) + + it("Should correctly populate the buffer list", () => { + manager.populateBufferList({ + current: event, + existingBuffers: [inactive1], + }) + + const buffers = manager.getBuffers() + expect(buffers.length).toBe(2) + expect(buffers.find(buffer => buffer instanceof InactiveBuffer)).toBeTruthy() + }) + + it("Should correctly format a comment string (based on neovim &comment option)", () => { + const buffer = manager.getBufferById("1") + const comment = "s1:/*,ex:*/,://,b:#,:%" + const formatted = buffer.formatCommentOption(comment) + expect(formatted.start.includes("/*")).toBeTruthy() + expect(formatted.end.includes("*/")).toBeTruthy() + expect(formatted.defaults.includes("//")).toBeTruthy() + expect(formatted.defaults.includes("#")).toBeTruthy() + }) + + it("should correctly pass input to a layer that implements a handler", () => { + store.getState.mockReturnValue({ + layers: { + "1": [ + { + handleInput: (key: string) => true, + isActive: () => true, + }, + ], + }, + }) + + const buffer = manager.getBufferById("1") + const canHandleInput = buffer.handleInput("h") + expect(canHandleInput).toBeTruthy() + }) + + it("should not allow handling of input if isActive does not exist on the layer", () => { + store.getState.mockReturnValue({ + layers: { + "1": [ + { + handleInput: (key: string) => true, + }, + ], + }, + }) + + const buffer = manager.getBufferById("1") + const canHandleInput = buffer.handleInput("h") + expect(canHandleInput).toBeFalsy() + }) + + it("should not intercept input for a layer that implements a handler but returns isActive = false", () => { + store.getState.mockReturnValue({ + layers: { + "1": [ + { + handleInput: (key: string) => true, + isActive: () => false, + }, + ], + }, + }) + const buffer = manager.getBufferById("1") + const canHandleInput = buffer.handleInput("h") + expect(canHandleInput).toBeFalsy() + }) +}) diff --git a/ui-tests/WelcomeCommandsView.test.tsx b/ui-tests/WelcomeCommandsView.test.tsx new file mode 100644 index 0000000000..a6bb012799 --- /dev/null +++ b/ui-tests/WelcomeCommandsView.test.tsx @@ -0,0 +1,68 @@ +import { shallow } from "enzyme" +import toJson from "enzyme-to-json" + +import * as React from "react" + +import { + WelcomeButton, + WelcomeCommandsView, +} from "./../browser/src/Editor/NeovimEditor/WelcomeBufferLayer" + +describe("Welcome Layer test", () => { + const buttons = [ + "button1", + "button2", + "button3", + "button4", + "button5", + "button6", + "button7", + "button8", + ] + + const commands = { + openFile: { command: "button1" }, + openTutor: { command: "button2" }, + openDocs: { command: "button3" }, + openConfig: { command: "button4" }, + openThemes: { command: "button5" }, + openWorkspaceFolder: { command: "button6" }, + commandPalette: { command: "button7" }, + commandline: { command: "button8" }, + } + const executeMock = jest.fn() + it("should render without crashing", () => { + const wrapper = shallow( + , + ) + expect(wrapper.length).toBe(1) + }) + + it("should match last snapshot on record", () => { + const wrapper = shallow( + , + ) + expect(toJson(wrapper)).toMatchSnapshot() + }) + it("should have the correct number of buttons for each commands", () => { + const wrapper = shallow( + , + ) + expect(wrapper.dive().find(WelcomeButton).length).toBe(Object.values(commands).length) + }) +}) diff --git a/ui-tests/WelcomeView.test.tsx b/ui-tests/WelcomeView.test.tsx new file mode 100644 index 0000000000..d312771da7 --- /dev/null +++ b/ui-tests/WelcomeView.test.tsx @@ -0,0 +1,87 @@ +import { mount } from "enzyme" +import { Event } from "oni-types" +import * as React from "react" + +import { + WelcomeView, + WelcomeViewProps, + WelcomeViewState, + IWelcomeInputEvent, +} from "./../browser/src/Editor/NeovimEditor/WelcomeBufferLayer" + +describe("", () => { + const buttons = [ + "button1", + "button2", + "button3", + "button4", + "button5", + "button6", + "button7", + "button8", + ] + + const commands = { + openFile: { command: "button1" }, + openTutor: { command: "button2" }, + openDocs: { command: "button3" }, + openConfig: { command: "button4" }, + openThemes: { command: "button5" }, + openWorkspaceFolder: { command: "button6" }, + commandPalette: { command: "button7" }, + commandline: { command: "button8" }, + } + + const executeCommand = jest.fn() + const inputEvent = new Event("handleInputTestEvent") + let handleInputSpy: jest.SpyInstance + + afterEach(() => { + if (handleInputSpy) { + handleInputSpy.mockClear() + } + instance.setState({ selectedId: "button1", currentIndex: 0 }) + }) + + const wrapper = mount( + , + ) + const instance = wrapper.instance() as WelcomeView + + it("Should render without crashing", () => { + expect(wrapper.length).toBe(1) + }) + + it("should default initial state to the first button id", () => { + expect(instance.state.selectedId).toBe("button1") + }) + + it("should correctly update selection based on input", () => { + instance.handleInput({ direction: 1, select: false }) + expect(instance.state.selectedId).toBe("button2") + }) + + it("should loop back to button if user navigates upwards at the first button", () => { + instance.handleInput({ direction: -1, select: false }) + expect(instance.state.currentIndex).toBe(7) + expect(instance.state.selectedId).toBe("button8") + }) + + it("should loop back to button if user navigates downwards at the last button", () => { + instance.setState({ currentIndex: 7, selectedId: "button8" }) + instance.handleInput({ direction: 1, select: false }) + expect(instance.state.currentIndex).toBe(0) + expect(instance.state.selectedId).toBe("button1") + }) + + it("should trigger a command on enter event", () => { + instance.handleInput({ direction: 0, select: true }) + expect(executeCommand.mock.calls[0][0]).toBe("button1") + }) +}) diff --git a/ui-tests/__snapshots__/WelcomeCommandsView.test.tsx.snap b/ui-tests/__snapshots__/WelcomeCommandsView.test.tsx.snap new file mode 100644 index 0000000000..9fef54e92b --- /dev/null +++ b/ui-tests/__snapshots__/WelcomeCommandsView.test.tsx.snap @@ -0,0 +1,83 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Welcome Layer test should match last snapshot on record 1`] = ` + + + + Quick Commands + + + + + + + + + Learn + + + + + + + Customize + + + + + +`; diff --git a/yarn.lock b/yarn.lock index ce4f81a73f..3ff809847d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7574,9 +7574,9 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -oni-api@0.0.48: - version "0.0.48" - resolved "https://registry.yarnpkg.com/oni-api/-/oni-api-0.0.48.tgz#531bfcbc72eeef48e250ae675cf8e0e0461e906c" +oni-api@0.0.49: + version "0.0.49" + resolved "https://registry.yarnpkg.com/oni-api/-/oni-api-0.0.49.tgz#4d2b7c23d63e6306e873d83de148b5f89856bb56" oni-core-logging@^1.0.0: version "1.0.0" @@ -9992,15 +9992,14 @@ style-loader@0.18.2: loader-utils "^1.0.2" schema-utils "^0.3.0" -styled-components@^3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-3.2.6.tgz#99e6e75a746bdedd295a17e03dd1493055a1cc3b" +styled-components@^3.4.4: + version "3.4.4" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-3.4.4.tgz#dbd2ea6338fb050b5b56783e189434fd7f18eda5" dependencies: buffer "^5.0.3" css-to-react-native "^2.0.3" fbjs "^0.8.16" hoist-non-react-statics "^2.5.0" - is-plain-object "^2.0.1" prop-types "^15.5.4" react-is "^16.3.1" stylis "^3.5.0"