Repository: onivim/oni
Branch: master
Commit: 17ceaa453119
Files: 800
Total size: 4.8 MB
Directory structure:
gitextract_k5a77194/
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE.md
│ └── config.yml
├── .gitignore
├── .gitmodules
├── .npmignore
├── .nvmrc
├── .oni/
│ ├── config.js
│ └── templates/
│ └── UnitTestTemplate.ts.template
├── .prettierignore
├── .prettierrc
├── .travis.yml
├── .vscode/
│ └── launch.json
├── .yarnrc
├── @types/
│ ├── color-normalize/
│ │ └── index.d.ts
│ └── font-manager/
│ └── index.d.ts
├── ACCOUNTING.md
├── BACKERS.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── appveyor.yml
├── browser/
│ ├── src/
│ │ ├── App.ts
│ │ ├── CSS.ts
│ │ ├── Constants.ts
│ │ ├── Editor/
│ │ │ ├── BufferHighlights.ts
│ │ │ ├── BufferManager.ts
│ │ │ ├── Editor.ts
│ │ │ ├── NeovimEditor/
│ │ │ │ ├── BufferLayerManager.ts
│ │ │ │ ├── CompletionMenu.ts
│ │ │ │ ├── Definition.ts
│ │ │ │ ├── FileDropHandler.tsx
│ │ │ │ ├── HoverRenderer.tsx
│ │ │ │ ├── NeovimActiveWindow.tsx
│ │ │ │ ├── NeovimBufferLayersView.tsx
│ │ │ │ ├── NeovimEditor.tsx
│ │ │ │ ├── NeovimEditorActions.ts
│ │ │ │ ├── NeovimEditorCommands.ts
│ │ │ │ ├── NeovimEditorLoadingOverlay.tsx
│ │ │ │ ├── NeovimEditorReducer.ts
│ │ │ │ ├── NeovimEditorSelectors.ts
│ │ │ │ ├── NeovimEditorStore.ts
│ │ │ │ ├── NeovimInput.tsx
│ │ │ │ ├── NeovimPopupMenu.tsx
│ │ │ │ ├── NeovimRenderer.tsx
│ │ │ │ ├── NeovimSurface.tsx
│ │ │ │ ├── Rename.tsx
│ │ │ │ ├── Symbols.ts
│ │ │ │ ├── ToolTipsProvider.ts
│ │ │ │ ├── WelcomeBufferLayer.tsx
│ │ │ │ ├── index.ts
│ │ │ │ └── markdown.ts
│ │ │ └── OniEditor/
│ │ │ ├── ColorHighlightLayer.tsx
│ │ │ ├── ImageBufferLayer.tsx
│ │ │ ├── IndentGuideBufferLayer.tsx
│ │ │ ├── OniEditor.tsx
│ │ │ ├── containers/
│ │ │ │ ├── BufferScrollBarContainer.ts
│ │ │ │ ├── DefinitionContainer.ts
│ │ │ │ └── ErrorsContainer.ts
│ │ │ └── index.ts
│ │ ├── Font.ts
│ │ ├── Grid.ts
│ │ ├── Input/
│ │ │ ├── KeyBindings.ts
│ │ │ ├── KeyParser.ts
│ │ │ ├── Keyboard/
│ │ │ │ ├── KeyboardLayout.ts
│ │ │ │ ├── KeyboardResolver.ts
│ │ │ │ ├── Resolvers.ts
│ │ │ │ └── index.ts
│ │ │ ├── KeyboardInput.tsx
│ │ │ └── Mouse.ts
│ │ ├── Performance.ts
│ │ ├── PeriodicJobs.ts
│ │ ├── PersistentStore.ts
│ │ ├── Platform.ts
│ │ ├── Plugins/
│ │ │ ├── AnonymousPlugin.ts
│ │ │ ├── Api/
│ │ │ │ ├── Capabilities.ts
│ │ │ │ ├── LanguageClient/
│ │ │ │ │ ├── LanguageClientHelpers.ts
│ │ │ │ │ └── LanguageClientLogger.ts
│ │ │ │ ├── Oni.ts
│ │ │ │ ├── Process.ts
│ │ │ │ ├── Services.ts
│ │ │ │ ├── Ui.ts
│ │ │ │ └── shell-env.d.ts
│ │ │ ├── PackageMetadataParser.ts
│ │ │ ├── Plugin.ts
│ │ │ ├── PluginConfigurationSynchronizer.ts
│ │ │ ├── PluginInstaller.ts
│ │ │ ├── PluginManager.ts
│ │ │ └── PluginSidebarPane.tsx
│ │ ├── Redux/
│ │ │ ├── LoggingMiddleware.ts
│ │ │ ├── RequestAnimationFrameNotifyBatcher.ts
│ │ │ ├── createStore.ts
│ │ │ └── index.ts
│ │ ├── Renderer/
│ │ │ ├── CanvasRenderer.ts
│ │ │ ├── INeovimRenderer.ts
│ │ │ ├── Span.ts
│ │ │ ├── WebGLRenderer/
│ │ │ │ ├── SolidRenderer.ts
│ │ │ │ ├── TextRenderer/
│ │ │ │ │ ├── GlyphAtlas/
│ │ │ │ │ │ ├── GlyphAtlas.ts
│ │ │ │ │ │ ├── IRasterizedGlyph.ts
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── ICellGroup.ts
│ │ │ │ │ ├── LigatureGrouper/
│ │ │ │ │ │ ├── ILigatureGrouper.ts
│ │ │ │ │ │ ├── NoopLigatureGrouper.ts
│ │ │ │ │ │ ├── OpenTypeLigatureGrouper.ts
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── TextRenderer.ts
│ │ │ │ │ ├── groupCells.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── WebGLRenderer.ts
│ │ │ │ ├── WebGLUtilities.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── normalizeColor.ts
│ │ │ └── index.ts
│ │ ├── Services/
│ │ │ ├── AutoClosingPairs.ts
│ │ │ ├── AutoUpdate.ts
│ │ │ ├── Automation.ts
│ │ │ ├── Bookmarks/
│ │ │ │ ├── BookmarksPane.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Browser/
│ │ │ │ ├── AddressBarView.tsx
│ │ │ │ ├── BrowserButtonView.tsx
│ │ │ │ ├── BrowserView.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── BrowserWindowConfigurationSynchronizer.ts
│ │ │ ├── Colors.ts
│ │ │ ├── CommandManager.ts
│ │ │ ├── Commands/
│ │ │ │ ├── GlobalCommands.ts
│ │ │ │ └── index.ts
│ │ │ ├── Completion/
│ │ │ │ ├── Completion.ts
│ │ │ │ ├── CompletionProviders.ts
│ │ │ │ ├── CompletionSelectors.ts
│ │ │ │ ├── CompletionState.ts
│ │ │ │ ├── CompletionStore.ts
│ │ │ │ ├── CompletionUtility.ts
│ │ │ │ ├── CompletionsRequestor.ts
│ │ │ │ └── index.ts
│ │ │ ├── Configuration/
│ │ │ │ ├── Configuration.ts
│ │ │ │ ├── ConfigurationCommands.ts
│ │ │ │ ├── ConfigurationEditor.ts
│ │ │ │ ├── DefaultConfiguration.ts
│ │ │ │ ├── DeprecatedConfigurationValues.ts
│ │ │ │ ├── FileConfigurationProvider.ts
│ │ │ │ ├── IConfigurationValues.ts
│ │ │ │ ├── PersistentSettings.ts
│ │ │ │ ├── ReasonConfiguration.ts
│ │ │ │ ├── UserConfiguration.ts
│ │ │ │ └── index.ts
│ │ │ ├── ContextMenu/
│ │ │ │ ├── ContextMenu.tsx
│ │ │ │ ├── ContextMenuComponent.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Debug.ts
│ │ │ ├── Diagnostics/
│ │ │ │ ├── index.ts
│ │ │ │ └── navigateErrors.ts
│ │ │ ├── DragAndDrop.tsx
│ │ │ ├── EditorManager.ts
│ │ │ ├── Explorer/
│ │ │ │ ├── ExplorerFileSystem.ts
│ │ │ │ ├── ExplorerSelectors.ts
│ │ │ │ ├── ExplorerSplit.tsx
│ │ │ │ ├── ExplorerStore.ts
│ │ │ │ ├── ExplorerView.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── FileIcon.tsx
│ │ │ ├── FileMappings.ts
│ │ │ ├── FileSystemWatcher/
│ │ │ │ └── index.ts
│ │ │ ├── FocusManager.ts
│ │ │ ├── IconThemes/
│ │ │ │ ├── IconThemeLoader.ts
│ │ │ │ ├── Icons.ts
│ │ │ │ ├── StyleWriter.ts
│ │ │ │ └── index.ts
│ │ │ ├── InputManager.ts
│ │ │ ├── KeyDisplayer/
│ │ │ │ ├── KeyDisplayer.tsx
│ │ │ │ ├── KeyDisplayerStore.ts
│ │ │ │ ├── KeyDisplayerView.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── Language/
│ │ │ │ ├── CodeAction.ts
│ │ │ │ ├── DefinitionRequestor.ts
│ │ │ │ ├── Edits.ts
│ │ │ │ ├── FindAllReferences.ts
│ │ │ │ ├── Formatting.ts
│ │ │ │ ├── HoverRequestor.ts
│ │ │ │ ├── LanguageClient.ts
│ │ │ │ ├── LanguageClientProcess.ts
│ │ │ │ ├── LanguageClientStatusBar.tsx
│ │ │ │ ├── LanguageClientTypes.ts
│ │ │ │ ├── LanguageConfiguration.ts
│ │ │ │ ├── LanguageEditorIntegration.ts
│ │ │ │ ├── LanguageManager.ts
│ │ │ │ ├── LanguageStore.ts
│ │ │ │ ├── PromiseQueue.ts
│ │ │ │ ├── RenameView.tsx
│ │ │ │ ├── ServerCapabilities.ts
│ │ │ │ ├── SignatureHelp.ts
│ │ │ │ ├── SignatureHelpView.tsx
│ │ │ │ ├── Workspace.ts
│ │ │ │ ├── addInsertModeLanguageFunctionality.ts
│ │ │ │ └── index.ts
│ │ │ ├── Learning/
│ │ │ │ ├── Achievements/
│ │ │ │ │ ├── AchievementNotificationRenderer.tsx
│ │ │ │ │ ├── AchievementsBufferLayer.tsx
│ │ │ │ │ ├── AchievementsManager.ts
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── LearningPane.tsx
│ │ │ │ ├── Tutorial/
│ │ │ │ │ ├── CompletionView.tsx
│ │ │ │ │ ├── GameplayBufferLayer.tsx
│ │ │ │ │ ├── GoalView.tsx
│ │ │ │ │ ├── ITutorial.ts
│ │ │ │ │ ├── Notes.tsx
│ │ │ │ │ ├── Stages/
│ │ │ │ │ │ ├── CompositeStage.tsx
│ │ │ │ │ │ ├── CorrectLineStage.tsx
│ │ │ │ │ │ ├── DeleteCharactersStage.tsx
│ │ │ │ │ │ ├── FadeInLineStage.tsx
│ │ │ │ │ │ ├── InitializeBufferStage.tsx
│ │ │ │ │ │ ├── MoveToGoalStage.tsx
│ │ │ │ │ │ ├── SetBufferStage.tsx
│ │ │ │ │ │ ├── SetCursorPositionStage.tsx
│ │ │ │ │ │ ├── WaitForModeStage.tsx
│ │ │ │ │ │ ├── WaitForRegisterStage.tsx
│ │ │ │ │ │ ├── WaitForStateStage.tsx
│ │ │ │ │ │ └── index.tsx
│ │ │ │ │ ├── TutorialBufferLayer.tsx
│ │ │ │ │ ├── TutorialGameplayManager.ts
│ │ │ │ │ ├── TutorialManager.ts
│ │ │ │ │ ├── Tutorials/
│ │ │ │ │ │ ├── BasicMovementTutorial.tsx
│ │ │ │ │ │ ├── BeginningsAndEndingsTutorial.tsx
│ │ │ │ │ │ ├── ChangeOperatorTutorial.tsx
│ │ │ │ │ │ ├── CopyPasteTutorial.tsx
│ │ │ │ │ │ ├── DeleteCharacterTutorial.tsx
│ │ │ │ │ │ ├── DeleteOperatorTutorial.tsx
│ │ │ │ │ │ ├── DotCommandTutorial.tsx
│ │ │ │ │ │ ├── InlineFindingTutorial.tsx
│ │ │ │ │ │ ├── InsertAndUndoTutorial.tsx
│ │ │ │ │ │ ├── SearchInBufferTutorial.tsx
│ │ │ │ │ │ ├── SwitchModeTutorial.tsx
│ │ │ │ │ │ ├── TargetsVimPluginTutorial.tsx
│ │ │ │ │ │ ├── TextObjectsTutorial.tsx
│ │ │ │ │ │ ├── VerticalMovementTutorial.tsx
│ │ │ │ │ │ ├── VisualModeTutorial.tsx
│ │ │ │ │ │ ├── WordMotionTutorial.tsx
│ │ │ │ │ │ └── index.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ └── index.ts
│ │ │ ├── Menu/
│ │ │ │ ├── Filter/
│ │ │ │ │ ├── FuseFilter.ts
│ │ │ │ │ ├── NoFilter.ts
│ │ │ │ │ ├── RegExFilter.ts
│ │ │ │ │ ├── Utils.ts
│ │ │ │ │ ├── VSCodeFilter.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── Menu.less
│ │ │ │ ├── Menu.ts
│ │ │ │ ├── MenuActionCreators.ts
│ │ │ │ ├── MenuActions.ts
│ │ │ │ ├── MenuComponent.tsx
│ │ │ │ ├── MenuReducer.ts
│ │ │ │ ├── MenuState.ts
│ │ │ │ ├── PinnedIconView.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Metadata.ts
│ │ │ ├── MultiProcess.ts
│ │ │ ├── Notifications/
│ │ │ │ ├── Notification.ts
│ │ │ │ ├── NotificationStore.ts
│ │ │ │ ├── Notifications.ts
│ │ │ │ ├── NotificationsView.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Overlay.ts
│ │ │ ├── Particles/
│ │ │ │ ├── ParticleSystem.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── Preview/
│ │ │ │ ├── PreviewBufferLayer.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── Recorder.ts
│ │ │ ├── Search/
│ │ │ │ ├── FinderProcess.ts
│ │ │ │ ├── RipGrep.ts
│ │ │ │ ├── Scorer/
│ │ │ │ │ ├── CharCode.ts
│ │ │ │ │ ├── Comparers.ts
│ │ │ │ │ ├── OniQuickOpenScorer.ts
│ │ │ │ │ ├── QuickOpenScorer.ts
│ │ │ │ │ ├── Utilities.ts
│ │ │ │ │ ├── filters.ts
│ │ │ │ │ └── strings.ts
│ │ │ │ ├── SearchPaneView.tsx
│ │ │ │ ├── SearchProvider.ts
│ │ │ │ ├── SearchResultsSpinnerView.tsx
│ │ │ │ ├── SearchTextBox.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── Sessions/
│ │ │ │ ├── SessionManager.ts
│ │ │ │ ├── Sessions.tsx
│ │ │ │ ├── SessionsPane.tsx
│ │ │ │ ├── SessionsStore.ts
│ │ │ │ └── index.ts
│ │ │ ├── Sidebar/
│ │ │ │ ├── SidebarContentSplit.tsx
│ │ │ │ ├── SidebarSplit.tsx
│ │ │ │ ├── SidebarStore.ts
│ │ │ │ ├── SidebarView.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Sneak/
│ │ │ │ ├── Sneak.tsx
│ │ │ │ ├── SneakStore.ts
│ │ │ │ ├── SneakView.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── Snippets/
│ │ │ │ ├── OniSnippet.ts
│ │ │ │ ├── SnippetBufferLayer.tsx
│ │ │ │ ├── SnippetCompletionProvider.ts
│ │ │ │ ├── SnippetManager.ts
│ │ │ │ ├── SnippetProvider.ts
│ │ │ │ ├── SnippetSession.ts
│ │ │ │ ├── SnippetVariableResolver.ts
│ │ │ │ ├── UserSnippetProvider.ts
│ │ │ │ └── index.ts
│ │ │ ├── StatusBar.ts
│ │ │ ├── SyntaxHighlighting/
│ │ │ │ ├── Definitions.ts
│ │ │ │ ├── GrammarLoader.ts
│ │ │ │ ├── ISyntaxHighlighter.ts
│ │ │ │ ├── SyntaxHighlightReconciler.ts
│ │ │ │ ├── SyntaxHighlightSelectors.ts
│ │ │ │ ├── SyntaxHighlighting.ts
│ │ │ │ ├── SyntaxHighlightingPeriodicJob.ts
│ │ │ │ ├── SyntaxHighlightingReducer.ts
│ │ │ │ ├── SyntaxHighlightingStore.ts
│ │ │ │ ├── TokenGenerator.tsx
│ │ │ │ ├── TokenScorer.ts
│ │ │ │ ├── TokenThemeProvider.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Tasks.ts
│ │ │ ├── Terminal.ts
│ │ │ ├── Themes/
│ │ │ │ ├── ThemeLoader.ts
│ │ │ │ ├── ThemeManager.ts
│ │ │ │ ├── ThemePicker.ts
│ │ │ │ └── index.ts
│ │ │ ├── TokenColors.ts
│ │ │ ├── TypingPredictionManager.ts
│ │ │ ├── UnhandledErrorMonitor.ts
│ │ │ ├── VersionControl/
│ │ │ │ ├── VersionControlBlameLayer.tsx
│ │ │ │ ├── VersionControlManager.tsx
│ │ │ │ ├── VersionControlPane.tsx
│ │ │ │ ├── VersionControlProvider.ts
│ │ │ │ ├── VersionControlStore.ts
│ │ │ │ ├── VersionControlView.tsx
│ │ │ │ └── index.ts
│ │ │ ├── VimConfigurationSynchronizer.ts
│ │ │ ├── WindowManager/
│ │ │ │ ├── LinearSplitProvider.ts
│ │ │ │ ├── RelationalSplitNavigator.ts
│ │ │ │ ├── SingleSplitProvider.ts
│ │ │ │ ├── WindowDock.ts
│ │ │ │ ├── WindowManager.ts
│ │ │ │ ├── WindowManagerStore.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── layoutFromSplitInfo.ts
│ │ │ └── Workspace/
│ │ │ ├── Workspace.ts
│ │ │ ├── WorkspaceCommands.ts
│ │ │ ├── WorkspaceConfiguration.ts
│ │ │ ├── find-up.d.ts
│ │ │ └── index.ts
│ │ ├── UI/
│ │ │ ├── Icon.tsx
│ │ │ ├── Shell/
│ │ │ │ ├── OverlayView.tsx
│ │ │ │ ├── Shell.tsx
│ │ │ │ ├── ShellActionCreators.ts
│ │ │ │ ├── ShellActions.ts
│ │ │ │ ├── ShellReducer.ts
│ │ │ │ ├── ShellState.ts
│ │ │ │ ├── ShellView.tsx
│ │ │ │ └── index.ts
│ │ │ └── components/
│ │ │ ├── Arrow.less
│ │ │ ├── Arrow.tsx
│ │ │ ├── Background.tsx
│ │ │ ├── BufferLayerHeader.tsx
│ │ │ ├── BufferScrollBar.tsx
│ │ │ ├── Caret.tsx
│ │ │ ├── CodeActions.tsx
│ │ │ ├── CommandLine.tsx
│ │ │ ├── Cursor.tsx
│ │ │ ├── CursorLine.tsx
│ │ │ ├── CursorPositioner.tsx
│ │ │ ├── Definition.tsx
│ │ │ ├── Error.tsx
│ │ │ ├── ErrorInfo.tsx
│ │ │ ├── ExternalMenus.tsx
│ │ │ ├── FlipCard.tsx
│ │ │ ├── HighlightText.tsx
│ │ │ ├── InstallHelp.less
│ │ │ ├── InstallHelp.tsx
│ │ │ ├── KeyBindingInfo.tsx
│ │ │ ├── LightweightText.tsx
│ │ │ ├── Loading.tsx
│ │ │ ├── LoadingSpinner.tsx
│ │ │ ├── Octicon.tsx
│ │ │ ├── PureComponentWithDisposeTracking.tsx
│ │ │ ├── QuickInfo.tsx
│ │ │ ├── QuickInfoContainer.tsx
│ │ │ ├── RedErrorScreen.tsx
│ │ │ ├── SectionTitle.tsx
│ │ │ ├── SidebarButton.tsx
│ │ │ ├── SidebarEmptyPaneView.tsx
│ │ │ ├── SidebarItemView.tsx
│ │ │ ├── Sneakable.tsx
│ │ │ ├── StatusBar.tsx
│ │ │ ├── StatusResize.tsx
│ │ │ ├── Tabs.tsx
│ │ │ ├── Text.tsx
│ │ │ ├── ToolTip.tsx
│ │ │ ├── VersionControl/
│ │ │ │ ├── Branch.tsx
│ │ │ │ ├── CommitMessage.tsx
│ │ │ │ ├── Commits.tsx
│ │ │ │ ├── File.tsx
│ │ │ │ ├── Help.tsx
│ │ │ │ ├── Staged.tsx
│ │ │ │ └── Status.tsx
│ │ │ ├── VimNavigator.tsx
│ │ │ ├── Visible.tsx
│ │ │ ├── WildMenu.tsx
│ │ │ ├── WindowSplitHost.tsx
│ │ │ ├── WindowSplits.tsx
│ │ │ ├── WindowTitle.tsx
│ │ │ ├── WithWidth.tsx
│ │ │ ├── animations.ts
│ │ │ ├── common.less
│ │ │ └── common.ts
│ │ ├── Utility.ts
│ │ ├── index.tsx
│ │ ├── neovim/
│ │ │ ├── CommandContext.ts
│ │ │ ├── EventContext.ts
│ │ │ ├── MsgPack.ts
│ │ │ ├── NeovimAutoCommands.ts
│ │ │ ├── NeovimBufferUpdateManager.ts
│ │ │ ├── NeovimInstance.ts
│ │ │ ├── NeovimMarks.ts
│ │ │ ├── NeovimProcessSpawner.ts
│ │ │ ├── NeovimTokenColorSynchronizer.ts
│ │ │ ├── NeovimWindowManager.ts
│ │ │ ├── QuickFix.ts
│ │ │ ├── Screen.ts
│ │ │ ├── ScreenWithPredictions.ts
│ │ │ ├── Session.ts
│ │ │ ├── SharedNeovimInstance.ts
│ │ │ ├── VimHighlights.ts
│ │ │ ├── actions.ts
│ │ │ └── index.ts
│ │ ├── neovim-client.d.ts
│ │ ├── overlay.less
│ │ ├── startEditors.ts
│ │ ├── sudo-prompt.d.ts
│ │ └── units-css.d.ts
│ ├── test/
│ │ ├── AppTests.ts
│ │ ├── Editor/
│ │ │ └── NeovimEditor/
│ │ │ ├── BufferStateTests.ts
│ │ │ ├── NeovimEditorReducerTests.ts
│ │ │ └── SymbolsTests.ts
│ │ ├── GridTests.ts
│ │ ├── Input/
│ │ │ ├── InputManagerTests.ts
│ │ │ ├── KeyParserTests.ts
│ │ │ └── Keyboard/
│ │ │ └── ResolverTests.ts
│ │ ├── MarkdownTests.ts
│ │ ├── Mocks/
│ │ │ ├── MockBuffer.ts
│ │ │ ├── MockPersistentStore.ts
│ │ │ ├── MockPluginManager.ts
│ │ │ ├── MockThemeLoader.ts
│ │ │ ├── index.ts
│ │ │ ├── neovim/
│ │ │ │ └── MockNeovimInstance.ts
│ │ │ └── neovim.ts
│ │ ├── Plugins/
│ │ │ └── Api/
│ │ │ └── ProcessTests.ts
│ │ ├── Renderer/
│ │ │ └── SpanTests.ts
│ │ ├── Services/
│ │ │ ├── AutoClosingPairsTests.ts
│ │ │ ├── Completion/
│ │ │ │ ├── CompletionProvidersTests.ts
│ │ │ │ ├── CompletionSelectorsTests.ts
│ │ │ │ ├── CompletionStoreTests.ts
│ │ │ │ ├── CompletionTests.ts
│ │ │ │ ├── CompletionUtilityTests.ts
│ │ │ │ └── CompletionsRequestorTests.ts
│ │ │ ├── Configuration/
│ │ │ │ ├── ConfigurationTests.ts
│ │ │ │ └── FileConfigurationProviderTests.ts
│ │ │ ├── Explorer/
│ │ │ │ ├── ExplorerFileSystemTests.ts
│ │ │ │ ├── ExplorerSelectorsTests.ts
│ │ │ │ └── ExplorerStoreTests.ts
│ │ │ ├── FileMappingsTests.ts
│ │ │ ├── Language/
│ │ │ │ ├── EditTests.ts
│ │ │ │ ├── LanguageEditorIntegrationTests.ts
│ │ │ │ └── LanguageManagerTests.ts
│ │ │ ├── Learning/
│ │ │ │ ├── Achievements/
│ │ │ │ │ └── AchievementsManagerTests.ts
│ │ │ │ └── Tutorial/
│ │ │ │ ├── TutorialGameplayManagerTests.ts
│ │ │ │ └── TutorialManagerTests.ts
│ │ │ ├── Menu/
│ │ │ │ └── MenuReducerTests.ts
│ │ │ ├── Notifications/
│ │ │ │ └── NotificationStoreTests.ts
│ │ │ ├── QuickOpen/
│ │ │ │ ├── FinderProcessTests.ts
│ │ │ │ ├── RegExFilterTests.ts
│ │ │ │ └── VSCodeFilterTests.ts
│ │ │ ├── Sneak/
│ │ │ │ └── SneakStoreTests.ts
│ │ │ ├── Snippets/
│ │ │ │ ├── OniSnippetTests.ts
│ │ │ │ ├── SnippetCompletionProviderTests.ts
│ │ │ │ ├── SnippetProviderTests.ts
│ │ │ │ ├── SnippetSessionTests.ts
│ │ │ │ └── SnippetVariableResolverTests.ts
│ │ │ ├── SyntaxHighlighting/
│ │ │ │ ├── SyntaxHighlightingReconcilerTests.ts
│ │ │ │ └── SyntaxHighlightingReducerTests.ts
│ │ │ ├── TokenColorsTests.ts
│ │ │ ├── TypingPredictionManagerTests.ts
│ │ │ ├── WindowManager/
│ │ │ │ ├── LinearSplitProviderTests.ts
│ │ │ │ ├── RelationalSplitNavigatorTests.ts.ts
│ │ │ │ ├── WindowManagerTests.ts
│ │ │ │ └── layoutFromSplitInfoTests.ts
│ │ │ └── Workspace/
│ │ │ └── WorkspaceConfigurationTests.ts
│ │ ├── Tabs/
│ │ │ └── TabsTest.tsx
│ │ ├── TestHelpers.ts
│ │ ├── UtilityTests.ts
│ │ └── neovim/
│ │ ├── NeovimBufferUpdateManagerTests.ts
│ │ ├── NeovimMarksTests.ts
│ │ ├── NeovimTokenColorSynchronizerTests.ts
│ │ └── ScreenWithPredictionsTest.ts
│ ├── testCoverageReporter.js
│ ├── testHelpers.js
│ ├── tsconfig.json
│ ├── tsconfig.test.json
│ ├── webpack.debug.config.js
│ ├── webpack.development.config.js
│ └── webpack.production.config.js
├── build/
│ ├── BuildSetupTemplate.js
│ ├── CopyIcons.js
│ ├── icon.icns
│ ├── script/
│ │ ├── CheckBinariesForBuild.js
│ │ ├── UploadDistributionBuildsToAzure.js
│ │ ├── appveyor-test.ps1
│ │ ├── install-reason.sh
│ │ ├── travis-build.sh
│ │ ├── travis-pack.sh
│ │ └── travis-test.sh
│ └── setup.template.iss
├── cli/
│ ├── linux/
│ │ └── oni.sh
│ ├── mac/
│ │ └── oni.sh
│ ├── src/
│ │ ├── cli.ts
│ │ └── cli_args.ts
│ ├── tsconfig.json
│ └── win/
│ └── oni.cmd
├── codecov.yml
├── configuration/
│ └── config.default.js
├── extensions/
│ ├── README.md
│ ├── clojure/
│ │ └── syntaxes/
│ │ └── clojure.tmLanguage.json
│ ├── csharp/
│ │ └── syntaxes/
│ │ └── csharp.tmLanguage.json
│ ├── css/
│ │ └── syntaxes/
│ │ └── css.tmLanguage.json
│ ├── elixir/
│ │ └── syntaxes/
│ │ ├── eex.tmLanguage.json
│ │ ├── elixir.tmLanguage.json
│ │ └── html(eex).tmLanguage.json
│ ├── go/
│ │ ├── README.md
│ │ └── syntaxes/
│ │ └── go.json
│ ├── html/
│ │ ├── package.json
│ │ └── snippets/
│ │ └── html.json
│ ├── images/
│ │ └── package.json
│ ├── java/
│ │ └── syntaxes/
│ │ └── Java.tmLanguage.json
│ ├── javascript/
│ │ ├── package.json
│ │ ├── snippets/
│ │ │ └── javascript.json
│ │ └── syntaxes/
│ │ ├── JavaScript.tmLanguage.json
│ │ └── JavaScriptReact.tmLanguage.json
│ ├── less/
│ │ └── syntaxes/
│ │ └── less.tmLanguage.json
│ ├── lua/
│ │ └── syntaxes/
│ │ └── lua.tmLanguage.json
│ ├── markdown/
│ │ └── syntaxes/
│ │ └── markdown.tmLanguage.json
│ ├── objective-c/
│ │ └── syntaxes/
│ │ ├── objective-c++.tmLanguage.json
│ │ └── objective-c.tmLanguage.json
│ ├── oni-plugin-markdown-preview/
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.tsx
│ │ └── tsconfig.json
│ ├── oni-plugin-prettier/
│ │ ├── .eslintrc.json
│ │ ├── index.js
│ │ ├── package.json
│ │ └── requirePackage.js
│ ├── oni-plugin-quickopen/
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── BookmarksSearch.ts
│ │ │ ├── QuickOpen.ts
│ │ │ ├── QuickOpenItem.ts
│ │ │ └── index.tsx
│ │ └── tsconfig.json
│ ├── php/
│ │ └── syntaxes/
│ │ ├── html.tmLanguage.json
│ │ └── php.tmLanguage.json
│ ├── python/
│ │ └── syntaxes/
│ │ └── python.tmLanguage.json
│ ├── reason/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── snippets/
│ │ │ └── reason.json
│ │ └── syntaxes/
│ │ └── reason.json
│ ├── ruby/
│ │ └── syntaxes/
│ │ └── ruby.tmLanguage.json
│ ├── rust/
│ │ └── syntaxes/
│ │ └── rust.tmLanguage.json
│ ├── scss/
│ │ └── syntaxes/
│ │ └── scss.json
│ ├── shell/
│ │ └── syntaxes/
│ │ └── shell.tmLanguage.json
│ ├── swift/
│ │ └── syntaxes/
│ │ └── swift.tmLanguage.json
│ ├── theme-dracula/
│ │ ├── colors/
│ │ │ ├── dracula.json
│ │ │ └── dracula.vim
│ │ └── package.json
│ ├── theme-gruvbox/
│ │ ├── colors/
│ │ │ ├── gruvbox.vim
│ │ │ ├── gruvbox_dark.json
│ │ │ └── gruvbox_light.json
│ │ └── package.json
│ ├── theme-hybrid/
│ │ ├── colors/
│ │ │ ├── hybrid.vim
│ │ │ ├── hybrid_dark.json
│ │ │ └── hybrid_light.json
│ │ └── package.json
│ ├── theme-icons-seti/
│ │ ├── README.md
│ │ ├── icons/
│ │ │ └── seti-icon-theme.json
│ │ ├── package.json
│ │ └── thirdpartynotices.txt
│ ├── theme-nord/
│ │ ├── README.md
│ │ ├── colors/
│ │ │ ├── nord.json
│ │ │ └── nord.vim
│ │ └── package.json
│ ├── theme-onedark/
│ │ ├── colors/
│ │ │ ├── onedark.json
│ │ │ └── onedark.vim
│ │ └── package.json
│ ├── theme-solarized/
│ │ ├── colors/
│ │ │ ├── solarized8.vim
│ │ │ ├── solarized8_dark.json
│ │ │ └── solarized8_light.json
│ │ └── package.json
│ ├── typescript/
│ │ ├── package.json
│ │ ├── snippets/
│ │ │ └── typescript.json
│ │ └── syntaxes/
│ │ ├── TypeScript.tmLanguage.json
│ │ └── TypeScriptReact.tmLanguage.json
│ └── vue/
│ └── syntaxes/
│ └── vue.json
├── font-awesome/
│ ├── css/
│ │ └── font-awesome.css
│ └── fonts/
│ └── FontAwesome.otf
├── index.dev.html
├── index.html
├── jest.config.js
├── main/
│ ├── src/
│ │ ├── Log.ts
│ │ ├── ProcessLifecycle.ts
│ │ ├── WindowManager.ts
│ │ ├── installDevTools.ts
│ │ ├── main.ts
│ │ └── menu.ts
│ ├── test/
│ │ └── WindowManagerTests.ts
│ ├── tsconfig.json
│ └── tsconfig.test.json
├── package.json
├── preload.js
├── scripts/
│ ├── dev_webpack_loader.js
│ └── webm2gif.sh
├── test/
│ ├── CiTests.ts
│ ├── Demo.ts
│ ├── Manual.md
│ ├── ci/
│ │ ├── Api.Buffer.AddLayer.tsx
│ │ ├── Api.Overlays.AddRemoveTest.tsx
│ │ ├── Assert.ts
│ │ ├── AutoClosingPairsTest.ts
│ │ ├── AutoCompletionTest-CSS.ts
│ │ ├── AutoCompletionTest-HTML.ts
│ │ ├── AutoCompletionTest-Reason.ts
│ │ ├── AutoCompletionTest-TypeScript.ts
│ │ ├── Browser.LocationTest.ts
│ │ ├── ColorHighlight.BufferLayerTest.ts
│ │ ├── Common.ts
│ │ ├── Configuration.JavaScriptEditorTest.ts
│ │ ├── Configuration.TypeScriptEditor.CompletionTest.ts
│ │ ├── Configuration.TypeScriptEditor.NewConfigurationTest.ts
│ │ ├── Editor.BufferModifiedState.ts
│ │ ├── Editor.BuffersCursorTest.ts
│ │ ├── Editor.CloseTabWithTabModesTabsTest.ts
│ │ ├── Editor.ExternalCommandLineTest.ts
│ │ ├── Editor.NextPreviousErrorTest.ts
│ │ ├── Editor.OpenFile.PathWithSpacesTest.ts
│ │ ├── Editor.ScrollEventTest.ts
│ │ ├── Editor.TabModifiedState.ts
│ │ ├── Explorer.LocateBufferTest.ts
│ │ ├── IndentGuide.BufferLayerTest.tsx
│ │ ├── LargeFileTest.ts
│ │ ├── LargePasteTest.ts
│ │ ├── MarkdownPreviewTest.tsx
│ │ ├── Neovim.CallOniCommands.ts
│ │ ├── Neovim.InvalidInitVimHandlingTest.ts
│ │ ├── NoInstalledNeovim.config.js
│ │ ├── NoInstalledNeovim.ts
│ │ ├── OSX.WindowTitleTest.ts
│ │ ├── PaintPerformanceTest.config.js
│ │ ├── PaintPerformanceTest.ts
│ │ ├── PrettierPluginTest.ts
│ │ ├── QuickOpenTest.ts
│ │ ├── Regression.1251.NoAdditionalProcessesOnStartup.ts
│ │ ├── Regression.1295.UnfocusedWindowTest.ts
│ │ ├── Regression.1296.SettingColorsTest.ts
│ │ ├── Regression.1799.MacroApplicationTest.ts
│ │ ├── Regression.1819.AutoReadCheckTimeTest.ts
│ │ ├── Regression.2047.VerifyCanvasIsIntegerSize.ts
│ │ ├── Sidebar.ToggleSplitTest.ts
│ │ ├── Snippets.BasicInsertTest.ts
│ │ ├── StatusBar-Mode.ts
│ │ ├── TabBarSneakTest.ts
│ │ ├── TextmateHighlighting.DebugScopesTest.ts
│ │ ├── TextmateHighlighting.ScopesOnEnterTest.ts
│ │ ├── TextmateHighlighting.TokenColorOverrideTest.ts
│ │ ├── Theming.LightAndDarkColorsTest.ts
│ │ ├── Welcome.BufferLayerTest.ts
│ │ ├── WindowManager.ErrorBoundary.tsx
│ │ ├── Workspace.ConfigurationTest.ts
│ │ └── initVimPromptNotificationTest.ts
│ ├── collateral/
│ │ └── 1799_test.csv
│ ├── common/
│ │ ├── Oni.ts
│ │ ├── ensureProcessNotRunning.ts
│ │ ├── index.ts
│ │ └── runInProcTest.ts
│ ├── demo/
│ │ ├── DemoCommon.ts
│ │ ├── HeroDemo.ts
│ │ └── HeroScreenshot.ts
│ ├── setup/
│ │ └── WindowsInstallerTests.ts
│ └── tsconfig.json
├── tslint.json
├── ui-tests/
│ ├── BrowserView.test.tsx
│ ├── BufferManager.test.ts
│ ├── BufferScrollBar.test.tsx
│ ├── CommandLine.test.tsx
│ ├── ContextMenuComponent.test.tsx
│ ├── ErrorInfo.test.tsx
│ ├── ExplorerSplit.test.tsx
│ ├── ExplorerView.test.tsx
│ ├── ExternalMenus.test.tsx
│ ├── HighlightText.test.tsx
│ ├── NeovimBufferLayersView.test.tsx
│ ├── NodeView.test.tsx
│ ├── NotificationView.test.tsx
│ ├── QuickInfo.test.tsx
│ ├── SessionManager.test.tsx
│ ├── Sessions.test.tsx
│ ├── SidebarStore.test.ts
│ ├── Tabs.test.tsx
│ ├── Text.test.tsx
│ ├── TokenScorer.test.ts
│ ├── TokenThemeProvider.test.tsx
│ ├── VersionControl/
│ │ ├── Help.test.tsx
│ │ ├── VersionControlCommits.test.tsx
│ │ ├── VersionControlComponents.test.tsx
│ │ ├── VersionControlManager.test.tsx
│ │ ├── VersionControlPane.test.tsx
│ │ ├── VersionControlSectionTitle.test.tsx
│ │ ├── VersionControlStore.test.ts
│ │ ├── VersionControlView.test.tsx
│ │ └── __snapshots__/
│ │ ├── VersionControlComponents.test.tsx.snap
│ │ ├── VersionControlSectionTitle.test.tsx.snap
│ │ └── VersionControlView.test.tsx.snap
│ ├── VersionControlBlameLayer.test.tsx
│ ├── VimNavigator.test.tsx
│ ├── WelcomeCommandsView.test.tsx
│ ├── WelcomeView.test.tsx
│ ├── WindowTitleView.test.tsx
│ ├── __snapshots__/
│ │ ├── BrowserView.test.tsx.snap
│ │ ├── BufferScrollBar.test.tsx.snap
│ │ ├── CommandLine.test.tsx.snap
│ │ ├── ErrorInfo.test.tsx.snap
│ │ ├── ExternalMenus.test.tsx.snap
│ │ ├── NodeView.test.tsx.snap
│ │ ├── NotificationView.test.tsx.snap
│ │ ├── QuickInfo.test.tsx.snap
│ │ ├── Tabs.test.tsx.snap
│ │ ├── Text.test.tsx.snap
│ │ ├── WelcomeCommandsView.test.tsx.snap
│ │ ├── WelcomeView.test.tsx.snap
│ │ └── WindowTitleView.test.tsx.snap
│ ├── enzyme-adapter-react-16.d.ts
│ ├── jestsetup.ts
│ ├── mocks/
│ │ ├── CommandManager.ts
│ │ ├── Configuration.ts
│ │ ├── EditorManager.ts
│ │ ├── MenuManager.ts
│ │ ├── Notifications.ts
│ │ ├── Oni.ts
│ │ ├── PersistentSettings.ts
│ │ ├── SharedNeovimInstance.ts
│ │ ├── Sidebar.ts
│ │ ├── Statusbar.ts
│ │ ├── UserConfiguration.ts
│ │ ├── Utility.ts
│ │ ├── Workspace.ts
│ │ ├── electronMock.ts
│ │ └── keyboardLayout.ts
│ ├── tsconfig.react.json
│ └── welcomeLayer.test.tsx
├── vim/
│ ├── core/
│ │ ├── colors/
│ │ │ └── Monokai.vim
│ │ ├── oni-core-interop/
│ │ │ ├── plugin/
│ │ │ │ └── init.vim
│ │ │ └── readme.md
│ │ ├── oni-core-statusbar/
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── oni-plugin-buffers/
│ │ │ ├── index.js
│ │ │ ├── jsconfig.json
│ │ │ └── package.json
│ │ ├── oni-plugin-git/
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ │ ├── index.tsx
│ │ │ │ └── vcs.ts
│ │ │ └── tsconfig.json
│ │ ├── oni-plugin-reasonml/
│ │ │ ├── ftdetect/
│ │ │ │ └── reason.vim
│ │ │ ├── indent/
│ │ │ │ └── reason.vim
│ │ │ └── syntax/
│ │ │ └── reason.vim
│ │ └── oni-plugin-typescript/
│ │ ├── .gitignore
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── CodeActions.ts
│ │ │ ├── Completion.ts
│ │ │ ├── Definition.ts
│ │ │ ├── FindAllReferences.ts
│ │ │ ├── Formatting.ts
│ │ │ ├── LightweightLanguageClient.ts
│ │ │ ├── QuickInfo.ts
│ │ │ ├── Rename.ts
│ │ │ ├── SignatureHelp.ts
│ │ │ ├── Symbols.ts
│ │ │ ├── TypeScriptConfigurationEditor.ts
│ │ │ ├── TypeScriptServerHost.ts
│ │ │ ├── Types.ts
│ │ │ ├── Utility.ts
│ │ │ └── index.ts
│ │ ├── tsconfig.json
│ │ └── tsconfig.test.json
│ ├── default/
│ │ └── bundle/
│ │ └── oni-vim-defaults/
│ │ └── plugin/
│ │ └── init.vim
│ └── noop.vim
└── webview_preload/
├── src/
│ └── index.ts
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
================================================
FILE: .gitattributes
================================================
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
# Declare text files that will always have LF line endings on checking
oni eol=lf
# Ignore the yarn library from Linguist, for the Github Language Stats.
lib/yarn/* linguist-vendored=true
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
**Oni Version:**
**Neovim Version (Linux only):**
**Operating System:**
**Issue:**
**Expected behavior:**
**Actual behavior:**
**Steps to reproduce:**
================================================
FILE: .github/config.yml
================================================
# Comment to be posted to on first time issues
newIssueWelcomeComment: >
Hello and welcome to the Oni repository! Thanks for opening your first issue here. To help us out, please make sure to include as much detail as possible - including screenshots and logs, if possible.
backers:
- 78856
- 1359421
- 4650931
- 13532591
- 5097613
- 22454918
- 347552
- 977348
- 28748
- 2835826
- 515720
- 124171
- 230476
- 10102132
- 10038688
- 817509
- 163128
- 4762
- 933251
- 3974037
- 141159
- 10263
- 3117205
- 5697723
- 6803419
- 1718128
- 2042893
- 14060883
- 244396
- 8832878
- 5127194
- 1764368
- 468548
- 2318955
- 28788713
- 1491574
- 6972449
- 20133970
- 5804765
- 360703
- 120710
- 4607311
- 7727602
- 9206426
- 119977
- 14045559
- 284789
- 2899448
- 2766423
================================================
FILE: .gitignore
================================================
# Yarn
yarn.lock
.DS_Store
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Python
.mypy_cache
# Local app data
$LOCALAPPDATA
# Output
lib
lib_test
dist
s3_dist
### https://raw.github.com/github/gitignore/2b3b1f428fb84dc4ba3ad2307ec44af3c5799848/Node.gitignore
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
build/*.ico
# Dependency directories
node_modules
jspm_packages
# Optional npm cache directory
.npm
# npm package-lock files
package-lock.json
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
### https://raw.github.com/github/gitignore/2b3b1f428fb84dc4ba3ad2307ec44af3c5799848/Global/Linux.gitignore
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### https://raw.github.com/github/gitignore/2b3b1f428fb84dc4ba3ad2307ec44af3c5799848/Global/macOS.gitignore
*.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### https://raw.github.com/github/gitignore/2b3b1f428fb84dc4ba3ad2307ec44af3c5799848/Global/Windows.gitignore
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# OCaml / Reason
.merlin
yarn.lock
# Webpack stats file
stats.json
# User Notes
.notes
================================================
FILE: .gitmodules
================================================
[submodule "vim/default/bundle/targets.vim"]
path = vim/default/bundle/targets.vim
url = https://github.com/wellle/targets.vim
[submodule "vim/default/bundle/vim-commentary"]
path = vim/default/bundle/vim-commentary
url = https://github.com/tpope/vim-commentary
[submodule "vim/default/bundle/vim-unimpaired"]
path = vim/default/bundle/vim-unimpaired
url = https://github.com/tpope/vim-unimpaired
[submodule "vim/default/bundle/vim-surround"]
path = vim/default/bundle/vim-surround
url = https://github.com/tpope/vim-surround.git
[submodule "vim/core/typescript-vim"]
path = vim/core/typescript-vim
url = https://github.com/leafgarland/typescript-vim
================================================
FILE: .npmignore
================================================
# Empty .npmignore to allow for including .gitignore'd built files
================================================
FILE: .nvmrc
================================================
9
================================================
FILE: .oni/config.js
================================================
// For more information on customizing Oni,
// check out our wiki page:
// https://github.com/onivim/oni/wiki/Configuration
const activate = oni => {
console.log("Oni config activated")
}
const deactivate = () => {
console.log("Oni config deactivated")
}
module.exports = {
activate,
deactivate,
"workspace.testFileMappings": [
{
sourceFolder: "browser/src",
mappedFolder: "browser/test",
mappedFileName: "${fileName}Tests.ts",
templateFilePath: ".oni/templates/UnitTestTemplate.ts.template",
},
],
}
================================================
FILE: .oni/templates/UnitTestTemplate.ts.template
================================================
/**
* ${TM_FILENAME_BASE}.ts
*/
import * as assert from "assert"
describe("${TM_FILENAME_BASE}", () => {
it("${1:tests}", async () => {
${2:assert.ok(false, "fail")}
${0}
})
})
================================================
FILE: .prettierignore
================================================
package.json
vim/core/oni-plugin-typescript/package.json
lib/yarn/*
================================================
FILE: .prettierrc
================================================
{
"printWidth": 100,
"semi": false,
"singleQuote": false,
"tabWidth": 4,
"trailingComma": "all"
}
================================================
FILE: .travis.yml
================================================
sudo: required
dist: trusty
language: node_js
branches:
only:
- master
- /^release.*/
cache:
directories:
- .oni_build_cache
matrix:
include:
- os: linux
sudo: required
dist: trusty
node_js: 8
- os: osx # OSX 10.12
node_js: 8
- os: osx # OSX 10.11
node_js: 8
osx_image: xcode8
allow_failures:
- osx_image: xcode8
addons:
apt:
packages:
- libxkbfile-dev
- libgnome-keyring-dev
- icnsutils
- graphicsmagick
- xz-utils
- rpm
- bsdtar
before_install:
- |
# Get the files modified in this commit, and check there is
# actual code changes made. If there isn't, stop the build.
MODIFIED_FILES=$(git diff --name-only $TRAVIS_COMMIT_RANGE)
if ! echo ${MODIFIED_FILES} | grep -qvE '(\.md$)/'; then
echo "Only documents were updated, stopping..."
exit
fi
install:
- npm install -g yarn@1.9.4
# Remove problematic version of yarn
- rm -rf ~/.yarn/bin
- yarn --version
- which yarn
- yarn install
script:
- npm run check-cached-binaries
- ./build/script/install-reason.sh
- ./build/script/travis-build.sh
- travis_wait ./build/script/travis-pack.sh
- ./build/script/travis-test.sh
deploy:
- provider: s3
access_key_id: AKIAIYMATI2CEFTHPBOQ
secret_access_key:
secure: S4f/aczEABGAMKk2tmVSkoGx+T2TLPmz5z6x6RKaM+eDmAaVSAELlIj1eAz6Tu2lv3jz+cpyAIISZNC/phORsJWwzbSZHVycLrMG0N3fDTqKFxu1fl6L3b3exRe9SiKXug73ZvHfktzd/XfRcgZKop4qgrwGiM57m0ZuZb/j1LkgjytTuvNAUxXbA84I8LZs/NhY17XuXq+KPlGElIHy3UFoGqQ8pBnTypkIU5rQTsoeAxXLBE8JAFfz+nBGZ7dx6OMbQcKX5jKh/gR3vk+4aTgV8gNE2Zp24ErjSqF2zly/gP9nE2DpfR7jqpZVHnb/v+OEjRDS80tLhPo8Dbibzwt2ZZNADpYBjSGtphwAmq4DCvJ7ORExOB5+O3wmXKQGdItyBTS7sW44n6BTyv87WxWuCaSDQ9QaO9PrbJdN5YGEYeRxSTM7Mn0t72IILkfFCUeSg6fl6tFs9iWIj5zltbxH1GQsRpA8j1Idg4O+894KnQABtw/YKh6rrdeYS9y/100qAjtV6qYyiP2IdPqMWGuasOiz87q3CQ8Ejd7uhiTjAaINVqos+0k04Yf5+rT4MqkeXnYFzjXuXcqDlpq6yJIZv3aD+PMSlZi2WmTYnPJXQFndHo/x9FhEh90UF9WdO5S27ySRSo8XQT4DyL3ToPkqz8y0slNmaNqiqMouQAU=
bucket: oni-media
local-dir: dist/media
acl: public_read
region: us-west-2
skip_cleanup: true
on:
condition: $TRAVIS_OS_NAME = osx
repo: onivim/oni
- provider: releases
api_key:
secure: qB2KX7c9gRf9HDNetUVJOSa1Lo81QJiukOChOEzGUkYzD/et/2uNgzl0AQX0jB6aOYNwtZAxTd/ON3TTbEWh90o+R1PmQUgQCZ8xIFjOwnQmuHFp4hHoOWNI/ahmQ3W5UD+gmkV5YTRDMfuNnRjraDcQ5R6744Gii4zHGBwnJQsKVh65rxChHfkAJ1WEoX0lUbEM9Veyof4W+xLEgf45eDNvG3fz2y11D2qcvJNckVdvaYIWFwVrefcmofnQvLoWhs8gs6tLBKxaieZ4DcKH+Q5ux+t2VT8LYOR6gkCzuBgUbGUB+AlfCrNR2T7H3LLONIbUMB8/3sF0+oojj9tXPoagHzmwL2gnE7esLxIXc90LbAMpzLwMDvOgA8YEIsgKKtM92BqMK3Pv2clDv+Wmu8Al/QOU8v4Zj5dF09pqa8VM95xPx8t5Harrz+AN6HhZtzoqceooCBtJaGDb5jRdIjWtg4LkJN82mMuNAcbTLUotWp9UxJyqiS+WLrF4cIjPBoq9AAay8XnqLJHpjLGq2Mfp8i9qRFJPr7m2a1WozUUL5/s8Fb7oVOm6rYodXP3ZrdF+0OFMMKoaMfxOg2IhKRIk+S6XYp64i8J4lOFJ0W0dg0ap+f3PsWTYaA7YQ+/SMSv0zsZdrprT80i/Mx5F5HjiljX6GDmanZCdEG1b2a8=
file_glob: true
file:
- dist/*.dmg
- dist/*.zip
skip_cleanup: true
on:
condition: $TRAVIS_OS_NAME = osx
tags: true
repo: onivim/oni
- provider: releases
api_key:
secure: qB2KX7c9gRf9HDNetUVJOSa1Lo81QJiukOChOEzGUkYzD/et/2uNgzl0AQX0jB6aOYNwtZAxTd/ON3TTbEWh90o+R1PmQUgQCZ8xIFjOwnQmuHFp4hHoOWNI/ahmQ3W5UD+gmkV5YTRDMfuNnRjraDcQ5R6744Gii4zHGBwnJQsKVh65rxChHfkAJ1WEoX0lUbEM9Veyof4W+xLEgf45eDNvG3fz2y11D2qcvJNckVdvaYIWFwVrefcmofnQvLoWhs8gs6tLBKxaieZ4DcKH+Q5ux+t2VT8LYOR6gkCzuBgUbGUB+AlfCrNR2T7H3LLONIbUMB8/3sF0+oojj9tXPoagHzmwL2gnE7esLxIXc90LbAMpzLwMDvOgA8YEIsgKKtM92BqMK3Pv2clDv+Wmu8Al/QOU8v4Zj5dF09pqa8VM95xPx8t5Harrz+AN6HhZtzoqceooCBtJaGDb5jRdIjWtg4LkJN82mMuNAcbTLUotWp9UxJyqiS+WLrF4cIjPBoq9AAay8XnqLJHpjLGq2Mfp8i9qRFJPr7m2a1WozUUL5/s8Fb7oVOm6rYodXP3ZrdF+0OFMMKoaMfxOg2IhKRIk+S6XYp64i8J4lOFJ0W0dg0ap+f3PsWTYaA7YQ+/SMSv0zsZdrprT80i/Mx5F5HjiljX6GDmanZCdEG1b2a8=
file_glob: true
file:
- dist/*.deb
- dist/*.rpm
- dist/*.tar.gz
skip_cleanup: true
on:
condition: $TRAVIS_OS_NAME = linux
tags: true
repo: onivim/oni
================================================
FILE: .vscode/launch.json
================================================
{
"version": "0.2.0",
"configurations": [
{
// Debug files that configure Electron (main.js, Menu.js, etc.)
"name": "Oni Main Process",
"type": "node",
"request": "launch",
"cwd": "${workspaceRoot}",
"program": "${workspaceRoot}/lib/main/src/main.js",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
},
"runtimeArgs": ["--enable-logging"],
"console": "internalConsole"
},
{
// Debug typescript files
// (must run `npm run build-debug` first to generate bundle.js.map)
"name": "Oni Application",
"type": "chrome", // <-- requires Extension "Debugger for Chrome"
"request": "launch",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
},
"runtimeArgs": ["--enable-logging", "${workspaceRoot}/lib/main/src/main.js"],
"webRoot": "${workspaceRoot}",
"sourceMaps": true,
"sourceMapPathOverrides": {
"webpack:///./*": "${webRoot}/*"
}
}
]
}
================================================
FILE: .yarnrc
================================================
--add.ignore-engines true
================================================
FILE: @types/color-normalize/index.d.ts
================================================
type ColorInput =
| string
| Int8Array
| Int16Array
| Int32Array
| Uint8Array
| Uint16Array
| Uint32Array
| Float32Array
| Float64Array
| Array
| Uint8ClampedArray
declare function colorNormalize(color: ColorInput, type: "float"): float[]
declare function colorNormalize(color: ColorInput, type: "array"): float[]
declare function colorNormalize(color: ColorInput, type: "int8"): Int8Array
declare function colorNormalize(color: ColorInput, type: "int16"): Int8Array
declare function colorNormalize(color: ColorInput, type: "int32"): Int8Array
declare function colorNormalize(color: ColorInput, type: "uint"): Uint8Array
declare function colorNormalize(color: ColorInput, type: "uint8"): Uint8Array
declare function colorNormalize(color: ColorInput, type: "uint16"): Uint8Array
declare function colorNormalize(color: ColorInput, type: "uint32"): Uint8Array
declare function colorNormalize(color: ColorInput, type: "float32"): Float32Array
declare function colorNormalize(color: ColorInput, type: "float64"): Float64Array
declare function colorNormalize(color: ColorInput, type: "uint_clamped"): Uint8ClampedArray
declare function colorNormalize(color: ColorInput, type: "uint8_clamped"): Uint8ClampedArray
declare function colorNormalize(color: ColorInput): float[]
declare module "color-normalize" {
export default colorNormalize
}
================================================
FILE: @types/font-manager/index.d.ts
================================================
type FontWeight = 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
type FontWidth = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
interface QueryFontDescriptor {
postscriptName?: string
family?: string
style?: string
weight?: FontWeight
width?: FontWidth
italic?: boolean
monospace?: boolean
}
interface ResultFontDescriptor {
path: string
postscriptName: string
family: string
style: string
weight: FontWeight
width: FontWidth
italic: boolean
monospace: boolean
}
interface FontManager {
getAvailableFonts: (callback: (availableFonts: ResultFontDescriptor[]) => void) => void
getAvailableFontsSync: () => ResultFontDescriptor[]
findFonts: (
fontDescriptor: QueryFontDescriptor,
callback: (foundFonts: ResultFontDescriptor[]) => void,
) => void
findFontsSync: (fontDescriptor: QueryFontDescriptor) => ResultFontDescriptor[]
findFont: (
fontDescriptor: QueryFontDescriptor,
callback: (foundFont: ResultFontDescriptor | null) => void,
) => void
findFontSync: (fontDescriptor: QueryFontDescriptor) => ResultFontDescriptor | null
substituteFont: (
postscriptName: string,
text: string,
callback: (replacement: ResultFontDescriptor[]) => void,
) => void
substituteFontSync: (postscriptName: string, text: string) => ResultFontDescriptor[]
}
declare module "font-manager" {
declare const fontManager: FontManager
export default fontManager
}
================================================
FILE: ACCOUNTING.md
================================================
# ONI
## Accounting
This file will contain a monthly report including:
* Incoming contributions
* How the contributions are distributed, in accordance with the project's goals
The initial plan for allocation is as follows:
* 10% - Vim - Contribute to Bram's charity of choice
* 20% - Neovim Development
* 35% - Paid to contributors via bounties
* 35% - Paid to maintainer
Your contributions help keep this project alive!
### March 2017
IN-PROGRESS
================================================
FILE: BACKERS.md
================================================
# Sponsors & Backers
Oni is an MIT-licensed open-source project. It's an independent project without the backing of a large company, and the ongoing development is made possible by our backers.
Thanks you to all our backers for making Oni possible!
## VIP Backers via BountySource
* @jordwalke
* @mhartington
* @MikaAK
* @emolitor
## VIP Backers via Patreon
* @mikl
* Tom Boland
* Simon Smith
* [JavaScript.Ninja](https://www.patreon.com/search?q=javascript.ninja)
* Mika Kalathil
* Franky Chung
* Jackie McGhee
## Backers via BountySource
* @adambard
* @akin_so
* @ayohan
* @badosu
* @josemarluedke
* @napcode
* @robtrac
* @rrichardson
* @sbuljac
* @parkerault
* @city41
* @nithesh
* @erandac
* @appelgriebsch
* Mateusz Wieloch
## Backers via PayPal
* @mchalkley
* @am2605
* Nathan Ensmenger
* Cesar Avitia
## Backers via OpenCollective
* Tal Amuyal
* Akinola Sowemimo
* Martijn Arts
* Amadeus Folego
* Kiyoshi Murata
* @Himura2la
* Frederick Gnodtke
## Backers via Patreon
* @bennettrogers
* @muream
* Johnnie Hård
* @robin-pham
* Ryan Campbell
* Balint Fulop
* Quasar Jarosz
* Channing Conger
* Clinton Bloodworth
* Lex Song
* Paul Baumgart
* Kaiden Sin
* Troy Vitullo
* Leo Critchley
* Patrick Massot
* Jerome Pellois
* Wesley Moore
* Kim Fiedler
* Nicolaus Hepler
* Nick Price
* Domenico Maisano
* Daniel Polanco
* Eric Hall
* Dimas Cyriaco
* Carlos Coves Prieto
* Bryan Germann
* James Herdman
* Wayan Jimmy
* Alex
* Phil Plückthun
* Norikazu Hayashi
* Paul Anderson
* Thomas Frick
* LinuxLefty
* Leon Bogaert
* Jorrit Siebelink
* Zac Sims
* Doug Beney
* Aditya Gudimella
* Michal Hantl
* Lennaert Meijvogel
* Jonas Strømsodd
* Trevor Barton
* Tercio de Melo
* Jon Plotner
* Patrick Ball
* Grégory Reinbold
* Antti Holvikari
* Christopher Auer
* Daniel Falk
* David Froger
* Dominic Saadi
* Gianni Chiappetta
* Jake Swanson
* James Herdman
* Jannis Kaiser
* Kosuke Hamada
* Lance
* Marius Gripsgard
* Matti Klock
* Selwyn
* Saito Nakamura
* artalar
* Jeff Hertzler
* Drew Lazzeri
* Bob Gunion
* Daniel Blanco
* Philip Larson
* Josemar Luedke
* Mateusz Wieloch
* Marc Agbanchenou
* Andrew Myers
* Corey T Kump
* Claudia Hardman
* Krakonos
* sschwarzer
* Andrew Cobby
* Imobach González Sosa
* Devin
* Antonio de Jesus Ochoa Solano
* Parker Ault
* Rauan Mayemir
* Matt Rockwell
* Anatoly
* Kino
* Andrey Popp
* James Atkinson
* Yohan Lee
* Sime Buljac
* Brian Recchia
* Brandon Ubben
* Devin
* sschwarzer
* Logan Call
* Adam Recvlohe
* Shawn MacIntyre
* Alexandre Mounton-Brady
* Todd Epple
* Jacob Mischka
* Javier Chavarri
* David Izquierdo
* Richou Degenne
* Tyler Compton
* Marcos Ojeda
* Daniel Martinez
* Anthony Mittaz
* Yohanes Bandung
* Jaap Frölich
* Aaron Franks
* Adam Howard
* Jeff See
* آرين دانشور
* Alpha Shuro
* Sundeep Malladi
* Sylvan
* Vitezslav Homolka
* Jens Aronsson
* Benjie Gillam
* Christophe Riolo
* Jih-Chi Lee
* Paul Naranja
* Krisztián Szegi
* Karlin Fox
* Wayne Maurer
* Ragnar Hardarson
* Andrew Herron
* TxH
* Richard Feldman
* ELLIOTCABLE
* Alexey Alekhin
* Gal Schlezinger
* Michael Jackson
* Tim Gebauer
* David Gregory
* Tony André Haugen
* Matthieu Tabuteau
* Rich Dean
* Dmytro Gladkyi
* Brett Eisenberg
* Oswaldo Caballero
* Goku
* Sawyer Bergeron
* Igor Matuszewski
* Visate
* Edward Vetter-Drake
* Steven Volocyk
* Danny Martini
* Mitchell Hanberg
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at admin@onivim.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
================================================
FILE: CONTRIBUTING.md
================================================
# Contribute
## Introduction
First, thank you for considering contributing to oni! It's people like you that make the open source community such a great community! 😊
We welcome any type of contribution, not only code. You can help with
* **QA**: file bug reports, the more details you can give the better (e.g. screenshots with the console open)
* **Marketing**: writing blog posts, howto's, printing stickers, ...
* **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, ...
* **Code**: take a look at the [open issues](issues). Even if you can't write code, commenting on them, showing that you care about a given issue matters. It helps us triage them.
* **Money**: we welcome financial contributions in full transparency on our [open collective](https://opencollective.com/oni).
## Your First Contribution
Working on your first Pull Request? You can learn how from this _free_ series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
## Submitting code
Any code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it. The pull request should also contain tests. We welcome and appreciate pull requests!
## Code review guidelines
* Keep PRs **small and scoped**. The bigger the pull request, the longer it will take to review and merge. Break down large pull requests into smaller incremental chunks - this will help catch issues earlier and be easier on both you and the maintainer.
* Following from the previous bullet point, **do not include unrelated changes in a PR**. It can be tempting to include extra styling changes or additional functionality, but these should be added as separate PRs.
* Think of each PR as **improving the quality of the codebase**. Codebases tend towards entropy and disorder unless actively managed - make sure that your change moves the quality needle in the right direction. This can take a variety of forms, including adding test coverage, reducing coupling, etc. _As we are a small team moving fast, we cannot afford to accumulate technical debt._
* If there is ambiguity in terms of design, architecture, or implementation, it's best to get **feedback before implementing**, to save both you and the maintainer time. If you're not sure, feel free to ask!
* For your first few PRs, **don't try and change the world** - pick some small issues and get familiar with the codebase. Then, work your way up to bigger issues - this will set you up for success.
PRs require approval from one other person, either a maintainer or a contributor. Keep in mind that when you approve code, you are accountable for it, too! Reviewers are the gatekeepers of quality and ensuring adherence to the guidelines above.
## Financial contributions
We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/oni).
Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed.
### Bounties
The primary allotment of our [open collective](https://opencollective.com/oni) budget is dedicated to bounties. Developing features and fixing bugs is a lot of work, and those go directly to the developers doing this work via bounties. It is the role of the _maintainer_ to set bounties and clear completion criteria. Issues that have a bounty associated with them will have a `bounty` label as well as an amount, ie, `bounty-50` means a $50 bounty.
* Guidelines:
* The fix for the bug/feature/issue _MUST_ be complete and _MUST_ be covered by tests to be eligible for a bounty.
* Any associated documentation relevant to the bug/feature _MUST_ be updated.
* If you begin working on an issue with an associated bounty, open a PR with "WIP" and the bug number in the title, as well as reference the issue #. This is important to reduce duplicate work.
#### Claiming a bounty
* Upon completion of an issue with an associated bounty, bounties are payable by [Submitting an Expense](https://opencollective.com/oni/expenses/new) on our [OpenCollective](https://opencollective.com/oni). Note that OpenCollective requires a PDF or Photo of an expense form for an expense claim to be accepted - more information, including an example expense form can be found [here](https://opencollective.com/faq#expense). Check out our [expenses](https://opencollective.com/oni/expenses#) page for an example.
* A collaborator will approve the expense once we have verified it meets the criteria outlined above (complete fix, covered by tests, associated documentation updated)
If you questions about the guidelines, please don't hesitate to contact the maintainer.
## Roles
There are various roles and responsibilities in managing an open-source project. Users that are active and have a positive impact on the project and community will be recognized and have the option of assuming additional responsibilities.
* **Maintainer** - A maintainer communicates goals and drives the vision for the project. The maintainer is responsible for breaking down hurdles and supporting contributors. In addition, the maintainer triages issues, produces releases, assigns bounties, and establishes completion criteria. Today, there is one maintainer, but that isn't a strict requirement.
* **Collaborator** - A collaborator is an established member of the project that is recognized for their impact and contributions. They can triage and close issues, approve PRs from other contributors / collaborators, and can approve expenses on our [open collective](https://opencollective.com/oni)
* **Contributor** - A contributor is a developer that has submitted a successful PR for the project.
### Becoming a collaborator
A collaborator is a contributor who has been recognized for the impact they've had on the project, over a sustained period of time. In general, this means the following:
* **Supporting the community** - helping others in issues and chat, supporting new developers, creating a positive and supportive environment.
* **Technical impact** - involvement in a core technical piece of the editor, or a broad impact on the ecosystem.
* **Positive and collaborative mindset** - creating good vibes, willingness to give and receive constructive feedback, being a team player.
## Questions
If you have any questions, create an [issue](issue) (protip: do a quick search first to see if someone else didn't ask the same question before!).
You can also reach us at hello@oni.opencollective.com.
## Credits
### Contributors
Thank you to all the people who have already contributed to oni!
### Backers
Thank you to all our backers! [[Become a backer](https://opencollective.com/oni#backer)]
### Sponsors
Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/oni#sponsor))
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2016
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
---
__NOTE:__ This repository is unmaintained - we are focusing on [Onivim 2](https://github.com/onivim/oni2) and [libvim](https://github.com/onivim/libvim).
---
Modern Modal Editing
## Introduction
Oni is a new kind of editor, focused on maximizing productivity - combining _modal editing_ with features you expect in modern editors. Oni is built with [neovim](https://github.com/neovim/neovim), and inspired by [VSCode](https://github.com/Microsoft/vscode), [Atom](https://atom.io/), [LightTable](http://lighttable.com/), and [Emacs](https://www.gnu.org/software/emacs/)
The vision of Oni is to build an editor that allows you to go from _thought to code_ as easily as possible - bringing together the raw editing power of Vim, the feature capabilities of Atom/VSCode, and a powerful and intuitive extensibility model - wrapped up in a beautiful package.
Check out [Releases](https://github.com/onivim/oni/releases) for the latest binaries, or [Build Oni](https://github.com/onivim/oni/wiki/Development) from source. Consider making a donation via [OpenCollective](https://opencollective.com/oni) [BountySource](https://salt.bountysource.com/teams/oni) if you find this project useful!
## Features
Oni brings several IDE-like integrations to neovim:
* [Embedded Browser](https://github.com/onivim/oni/wiki/Features#embedded-browser)
* [Quick Info](https://github.com/onivim/oni/wiki/Features#quick-info)
* [Code Completion](https://github.com/onivim/oni/wiki/Features#code-completion)
* [Syntax / Compilation Errors](https://github.com/onivim/oni/wiki/Features#syntax--compilation-errors)
* [Fuzzy Finding](https://github.com/onivim/oni/wiki/Features#fuzzy-finder)
* [Status Bar](https://github.com/onivim/oni/wiki/Features#status-bar)
* [Interactive Tutorial](https://github.com/onivim/oni/wiki/Features#interactive-tutorial)
And more coming - check out our [Roadmap](https://github.com/onivim/oni/wiki/Roadmap)
Oni is cross-platform and supports Windows, Mac, and Linux.
> If you're a Vim power user, and don't need all these features, check out our [minimal configuration](https://github.com/onivim/oni/wiki/How-To:-Minimal-Oni-Configuration).
## Installation
We have installation guides for each platform:
* [Windows](https://github.com/onivim/oni/wiki/Installation-Guide#windows)
* [Mac](https://github.com/onivim/oni/wiki/Installation-Guide#mac)
* [Linux](https://github.com/onivim/oni/wiki/Installation-Guide#linux)
The latest binaries are available on our [Releases](https://github.com/onivim/oni/releases) page, and if you'd prefer to build from source, check out our [Development](https://github.com/onivim/oni/wiki/Development) guide.
## Goals
The goal of this project is to provide both the full-fledged Vim experience, with no compromises, while pushing forward to enable new productivity scenarios.
* **Modern UX** - The Vim experience should not be compromised by terminal limitations.
* **Rich plugin development** - using JavaScript, instead of VimL.
* **Cross-platform support** - across Windows, OS X, and Linux.
* **Batteries included** - rich features are available out of the box - minimal setup needed to be productive.
* **Performance** - no compromises, Vim is fast, and Oni should be fast too.
* **Ease Learning Curve** - without sacrificing the Vim experience.
Vim is an incredible tool for manipulating _text_ at the speed of thought. With a composable, modal command language, it is no wonder that Vim usage is still prevalent today.
However, going from thought to _code_ has some different challenges than going from thought to _text_. Code editors today provide several benefits that help to reduce **cognitive load** when writing code, and that benefit is tremendously important - not only in terms of pure coding efficiency and productivity, but also in making the process of writing code enjoyable and fun.
The goal of this project is to give an editor that gives the best of both worlds - the power, speed, and flexibility of using Vim for manipulating text, as well as the rich tooling that comes with an IDE. We want to make coding as efficient, fast, and fun as we can!
## Documentation
* Check out the [Wiki](https://github.com/onivim/oni/wiki) for documentation on how to use and modify Oni.
* [FAQ](https://github.com/onivim/oni/wiki/FAQ)
* [Roadmap](https://github.com/onivim/oni/wiki/Roadmap)
## Available Plugins
Some available plugins created by Oni users are listed below (if you'd like to add your
plugin to this list please create a PR updating this **README** with the details).
* [Oni Touchbar Plugin](https://github.com/jordan-arenstein/oni-plugin-touchbar) - by [jordan-arenstein](https://github.com/jordan-arenstein?tab=overview&from=2018-07-01&to=2018-07-31)
* [quickFind](https://github.com/marene/quickFind) - by [marene](https://github.com/marene)
* Themes
* [Night Owl](https://github.com/Akin909/oni-theme-night-owl)
## Contributing
There many ways to get involved & contribute to Oni:
* Thumbs up existing [issues](https://github.com/onivim/oni/issues) if they impact you.
* [Create an issue](https://github.com/onivim/oni/issues) for bugs or new features.
* Review and update our [documentation](https://github.com/onivim/oni/wiki).
* Try out the latest [released build](https://github.com/onivim/oni/releases).
* Help us [develop](https://github.com/onivim/oni/wiki/Development):
* Review [PRs](https://github.com/onivim/oni/pulls)
* Submit a bug fix or feature
* Add test cases
* Create a blog post or YouTube video
* Follow us on [Twitter](https://twitter.com/oni_vim)
## Acknowledgements
Oni is an independent project and is made possible by the support of some exceptional people. Big thanks to the following people for helping to realize this project:
* the [neovim team](https://neovim.io/), especially [justinmk](https://github.com/justinmk) and [tarruda](https://github.com/tarruda) - Oni would not be possible without their vision
* [jordwalke](https://github.com/jordwalke) for his generous support, inspiration, and ideas. And React ;)
* [keforbes](https://github.com/keforbes) for helping to get this project off the ground
* [Akin909](https://github.com/Akin909) for his extensive contributions
* [CrossR](https://github.com/CrossR) for polishing features and configurations
* [Cryza](https://github.com/Cryza) for the webgl renderer
* [tillarnold](https://github.com/tillarnold) for giving us the `oni` npm package name
* [mhartington](https://github.com/mhartington) for his generous support
* [badosu](https://github.com/badosu) for his support, contributions, and managing the AUR releases
* All our current monthly [sponsors](https://salt.bountysource.com/teams/oni/supporters) and [backers](BACKERS.md)
* All of our [contributors](https://github.com/onivim/oni/graphs/contributors) - thanks for helping to improve this project!
Several other great neovim front-end UIs [here](https://github.com/neovim/neovim/wiki/Related-projects) served as a reference, especially [NyaoVim](https://github.com/rhysd/NyaoVim) and [VimR](https://github.com/qvacua/vimr). I encourage you to check those out!
Thank you!
## Contributors
This project exists thanks to all the people who have [contributed](CONTRIBUTING.md):
## License
MIT License. Copyright (c) Bryan Phelps
Windows and OSX have a bundled version of Neovim, which is covered under [Neovim's license](https://github.com/neovim/neovim/blob/master/LICENSE)
### Bundled Plugins
Bundled plugins have their own license terms. These include:
* [typescript-vim](https://github.com/leafgarland/typescript-vim) (`oni/vim/core/typescript.vim`)
* [targets.vim](https://github.com/wellle/targets.vim) (`oni/vim/default/bundle/targets.vim`)
* [vim-commentary](https://github.com/tpope/vim-commentary) (`oni/vim/default/bundle/vim-commentary`)
* [vim-unimpaired](https://github.com/tpope/vim-unimpaired) (`oni/vim/default/bundle/vim-unimpaired`)
* [vim-surround](https://github.com/tpope/vim-surround) (`oni/vim/default/bundle/vim-surround`)
* [vim-reasonml](https://github.com/reasonml-editor/vim-reason) (`.vim` files in `oni/vim/core/oni-plugin-reasonml`)
================================================
FILE: appveyor.yml
================================================
# Test against the latest version of this Node.js version
environment:
nodejs_version: "8"
os: unstable
branches:
only:
- master
- /^release.*/
# Skip CI build if the changes match these rules exactly.
# Ie, if the BACKERS.md file is changed, we don't need to build.
skip_commits:
files:
- "**/*.md"
cache:
- .oni_build_cache -> package.json
platform:
- x86
- x64
# Install scripts. (runs after repo cloning)
install:
# Ensure the Git Submoduldes have been pulled down too
- git submodule update --init --recursive
# Get the latest stable version of Node.js or io.js
- ps: Install-Product node $env:nodejs_version
# Workaround https://github.com/npm/npm/issues/18380
- npm install -g yarn
- node --version
- npm --version
# install modules
- yarn install
- yarn run check-cached-binaries
# Post-install test scripts.
test_script:
- powershell build/script/appveyor-test.ps1
# Don't actually build.
build: off
================================================
FILE: browser/src/App.ts
================================================
/**
* App.ts
*
* Entry point for the Oni application - managing the overall lifecycle
*/
import { ipcRenderer, remote } from "electron"
import * as fs from "fs"
import * as minimist from "minimist"
import * as path from "path"
import { IDisposable } from "oni-types"
import * as Log from "oni-core-logging"
import * as Performance from "./Performance"
import * as Utility from "./Utility"
import { IConfigurationValues } from "./Services/Configuration/IConfigurationValues"
const editorManagerPromise = import("./Services/EditorManager")
const sharedNeovimInstancePromise = import("./neovim/SharedNeovimInstance")
export type QuitHook = () => Promise
let _quitHooks: QuitHook[] = []
const _initializePromise: Utility.ICompletablePromise = Utility.createCompletablePromise<
void
>()
export const registerQuitHook = (quitHook: QuitHook): IDisposable => {
_quitHooks.push(quitHook)
const dispose = () => {
_quitHooks = _quitHooks.filter(qh => qh !== quitHook)
}
return {
dispose,
}
}
export const quit = async (): Promise => {
Log.info(`[App::quit] called with ${_quitHooks.length} quitHooks`)
const promises = _quitHooks.map(async qh => {
Log.info("[App.quit] Waiting for quit hook...")
await qh()
Log.info("[App.quit] Quit hook completed successfully")
})
await Promise.all([promises])
// On mac we should quit the application when the user press Cmd + Q
if (process.platform === "darwin") {
Log.info("[App::quit] quitting app")
remote.app.quit()
}
Log.info("[App::quit] completed")
}
export const waitForStart = (): Promise => {
return _initializePromise.promise
}
export const start = async (args: string[]): Promise => {
Performance.startMeasure("Oni.Start")
const UnhandledErrorMonitor = await import("./Services/UnhandledErrorMonitor")
UnhandledErrorMonitor.activate()
const Shell = await import("./UI/Shell")
Shell.activate()
const configurationPromise = import("./Services/Configuration")
const configurationCommandsPromise = import("./Services/Configuration/ConfigurationCommands")
const debugPromise = import("./Services/Debug")
const pluginManagerPromise = import("./Plugins/PluginManager")
const themesPromise = import("./Services/Themes")
const iconThemesPromise = import("./Services/IconThemes")
const sessionManagerPromise = import("./Services/Sessions")
const sidebarPromise = import("./Services/Sidebar")
const overlayPromise = import("./Services/Overlay")
const statusBarPromise = import("./Services/StatusBar")
const startEditorsPromise = import("./startEditors")
const menuPromise = import("./Services/Menu")
const browserWindowConfigurationSynchronizerPromise = import("./Services/BrowserWindowConfigurationSynchronizer")
const colorsPromise = import("./Services/Colors")
const tokenColorsPromise = import("./Services/TokenColors")
const diagnosticsPromise = import("./Services/Diagnostics")
const globalCommandsPromise = import("./Services/Commands/GlobalCommands")
const inputManagerPromise = import("./Services/InputManager")
const languageManagerPromise = import("./Services/Language")
const vcsManagerPromise = import("./Services/VersionControl")
const notificationsPromise = import("./Services/Notifications")
const snippetPromise = import("./Services/Snippets")
const keyDisplayerPromise = import("./Services/KeyDisplayer")
const taksPromise = import("./Services/Tasks")
const terminalPromise = import("./Services/Terminal")
const workspacePromise = import("./Services/Workspace")
const workspaceCommandsPromise = import("./Services/Workspace/WorkspaceCommands")
const windowManagerPromise = import("./Services/WindowManager")
const multiProcessPromise = import("./Services/MultiProcess")
const themePickerPromise = import("./Services/Themes/ThemePicker")
const cssPromise = import("./CSS")
const completionProvidersPromise = import("./Services/Completion/CompletionProviders")
const parsedArgs = minimist(args, { string: "_" })
const currentWorkingDirectory = process.cwd()
const normalizedFiles = parsedArgs._.map(
arg => (path.isAbsolute(arg) ? arg : path.join(currentWorkingDirectory, arg)),
)
const filesToOpen = normalizedFiles.filter(f => {
if (fs.existsSync(f)) {
return fs.statSync(f).isFile()
} else {
return true
}
})
const foldersToOpen = normalizedFiles.filter(
f => fs.existsSync(f) && fs.statSync(f).isDirectory(),
)
Log.info("Files to open: " + JSON.stringify(filesToOpen))
Log.info("Folders to open: " + JSON.stringify(foldersToOpen))
let workspaceToLoad = null
// If a folder has been specified, we'll change directory to it
if (foldersToOpen.length > 0) {
workspaceToLoad = foldersToOpen[0]
} else if (filesToOpen.length > 0) {
workspaceToLoad = path.dirname(filesToOpen[0])
}
// Helper for debugging:
Performance.startMeasure("Oni.Start.Config")
const { configuration } = await configurationPromise
const initialConfigParsingErrors = configuration.getErrors()
if (initialConfigParsingErrors && initialConfigParsingErrors.length > 0) {
initialConfigParsingErrors.forEach((err: Error) => Log.error(err))
}
const configChange = (newConfigValues: Partial) => {
let prop: keyof IConfigurationValues
for (prop in newConfigValues) {
if (newConfigValues[prop]) {
Shell.Actions.setConfigValue(prop, newConfigValues[prop])
}
}
}
configuration.start()
configChange(configuration.getValues()) // initialize values
configuration.onConfigurationChanged.subscribe(configChange)
Performance.endMeasure("Oni.Start.Config")
const PluginManager = await pluginManagerPromise
PluginManager.activate(configuration)
const pluginManager = PluginManager.getInstance()
const developmentPlugin = parsedArgs["plugin-develop"]
let developmentPluginError: { title: string; errorText: string }
if (typeof developmentPlugin === "string") {
Log.info("Registering development plugin: " + developmentPlugin)
if (fs.existsSync(developmentPlugin)) {
pluginManager.addDevelopmentPlugin(developmentPlugin)
} else {
developmentPluginError = {
title: "Error parsing arguments",
errorText: "Could not find plugin: " + developmentPlugin,
}
Log.warn(developmentPluginError.errorText)
}
} else if (typeof developmentPlugin === "boolean") {
developmentPluginError = {
title: "Error parsing arguments",
errorText: "--plugin-develop must be followed by a plugin path",
}
Log.warn(developmentPluginError.errorText)
}
Performance.startMeasure("Oni.Start.Plugins.Discover")
pluginManager.discoverPlugins()
Performance.endMeasure("Oni.Start.Plugins.Discover")
const oniApi = pluginManager.getApi()
Performance.startMeasure("Oni.Start.Themes")
const Themes = await themesPromise
const IconThemes = await iconThemesPromise
await Promise.all([
Themes.activate(configuration, pluginManager),
IconThemes.activate(configuration, pluginManager),
])
const Colors = await colorsPromise
Colors.activate(configuration, Themes.getThemeManagerInstance())
const colors = Colors.getInstance()
Shell.initializeColors(Colors.getInstance())
Performance.endMeasure("Oni.Start.Themes")
const TokenColors = await tokenColorsPromise
TokenColors.activate(configuration, Themes.getThemeManagerInstance())
const BrowserWindowConfigurationSynchronizer = await browserWindowConfigurationSynchronizerPromise
BrowserWindowConfigurationSynchronizer.activate(configuration, Colors.getInstance())
const { editorManager } = await editorManagerPromise
const Workspace = await workspacePromise
Workspace.activate(configuration, editorManager, workspaceToLoad)
const workspace = Workspace.getInstance()
const WindowManager = await windowManagerPromise
const MultiProcess = await multiProcessPromise
MultiProcess.activate(WindowManager.windowManager)
const StatusBar = await statusBarPromise
StatusBar.activate(configuration)
const Overlay = await overlayPromise
Overlay.activate()
const overlayManager = Overlay.getInstance()
const sneakPromise = import("./Services/Sneak")
const { commandManager } = await import("./Services/CommandManager")
const Sneak = await sneakPromise
Sneak.activate(colors, commandManager, configuration, overlayManager)
const Menu = await menuPromise
Menu.activate(configuration, overlayManager)
const menuManager = Menu.getInstance()
const Notifications = await notificationsPromise
Notifications.activate(configuration, overlayManager)
const notifications = Notifications.getInstance()
if (typeof developmentPluginError !== "undefined") {
const notification = notifications.createItem()
notification.setContents(developmentPluginError.title, developmentPluginError.errorText)
notification.setLevel("error")
notification.onClick.subscribe(() =>
commandManager.executeCommand("oni.config.openConfigJs"),
)
notification.show()
}
configuration.onConfigurationError.subscribe(err => {
const notification = notifications.createItem()
notification.setContents("Error Loading Configuration", err.toString())
notification.setLevel("error")
notification.onClick.subscribe(() =>
commandManager.executeCommand("oni.config.openConfigJs"),
)
notification.show()
})
UnhandledErrorMonitor.start(configuration, Notifications.getInstance())
const Tasks = await taksPromise
Tasks.activate(menuManager)
const tasks = Tasks.getInstance()
const LanguageManager = await languageManagerPromise
LanguageManager.activate(oniApi)
const languageManager = LanguageManager.getInstance()
Performance.startMeasure("Oni.Start.Editors")
const SharedNeovimInstance = await sharedNeovimInstancePromise
const { startEditors } = await startEditorsPromise
const CSS = await cssPromise
CSS.activate()
const Snippets = await snippetPromise
Snippets.activate(commandManager, configuration)
Shell.Actions.setLoadingComplete()
const Diagnostics = await diagnosticsPromise
const diagnostics = Diagnostics.getInstance()
const CompletionProviders = await completionProvidersPromise
CompletionProviders.activate(languageManager)
const initializeAllEditors = async () => {
await startEditors(
filesToOpen,
Colors.getInstance(),
CompletionProviders.getInstance(),
configuration,
diagnostics,
languageManager,
menuManager,
overlayManager,
pluginManager,
Snippets.getInstance(),
Themes.getThemeManagerInstance(),
TokenColors.getInstance(),
workspace,
)
await SharedNeovimInstance.activate(configuration, pluginManager)
}
await Promise.race([Utility.delay(5000), initializeAllEditors()])
Performance.endMeasure("Oni.Start.Editors")
Performance.startMeasure("Oni.Start.Sidebar")
const Sidebar = await sidebarPromise
const Learning = await import("./Services/Learning")
const Explorer = await import("./Services/Explorer")
const Search = await import("./Services/Search")
Sidebar.activate(configuration, workspace)
const sidebarManager = Sidebar.getInstance()
const VCSManager = await vcsManagerPromise
VCSManager.activate(oniApi, sidebarManager, notifications)
Explorer.activate(oniApi, configuration, Sidebar.getInstance())
Learning.activate(
commandManager,
configuration,
editorManager,
overlayManager,
Sidebar.getInstance(),
WindowManager.windowManager,
)
const Sessions = await sessionManagerPromise
Sessions.activate(oniApi, sidebarManager)
Performance.endMeasure("Oni.Start.Sidebar")
const createLanguageClientsFromConfiguration =
LanguageManager.createLanguageClientsFromConfiguration
diagnostics.start(languageManager)
const Browser = await import("./Services/Browser")
Browser.activate(commandManager, configuration, editorManager)
Performance.startMeasure("Oni.Start.Activate")
const api = pluginManager.startApi()
Search.activate(api)
configuration.activate(api)
Snippets.activateProviders(
commandManager,
CompletionProviders.getInstance(),
configuration,
pluginManager,
)
createLanguageClientsFromConfiguration(configuration.getValues())
const { inputManager } = await inputManagerPromise
const autoClosingPairsPromise = import("./Services/AutoClosingPairs")
const ConfigurationCommands = await configurationCommandsPromise
ConfigurationCommands.activate(commandManager, configuration, editorManager)
const AutoClosingPairs = await autoClosingPairsPromise
AutoClosingPairs.activate(configuration, editorManager, inputManager, languageManager)
const GlobalCommands = await globalCommandsPromise
GlobalCommands.activate(commandManager, editorManager, menuManager, tasks)
const Debug = await debugPromise
Debug.activate(commandManager)
const WorkspaceCommands = await workspaceCommandsPromise
WorkspaceCommands.activateCommands(
configuration,
editorManager,
Snippets.getInstance(),
workspace,
)
const Preview = await import("./Services/Preview")
Preview.activate(commandManager, configuration, editorManager)
const KeyDisplayer = await keyDisplayerPromise
KeyDisplayer.activate(
commandManager,
configuration,
editorManager,
inputManager,
overlayManager,
)
const ThemePicker = await themePickerPromise
ThemePicker.activate(configuration, menuManager, Themes.getThemeManagerInstance())
const Bookmarks = await import("./Services/Bookmarks")
Bookmarks.activate(configuration, editorManager, Sidebar.getInstance())
const PluginsSidebarPane = await import("./Plugins/PluginSidebarPane")
PluginsSidebarPane.activate(commandManager, configuration, pluginManager, sidebarManager)
const Terminal = await terminalPromise
Terminal.activate(commandManager, configuration, editorManager)
const Particles = await import("./Services/Particles")
Particles.activate(commandManager, configuration, editorManager, overlayManager)
const PluginConfigurationSynchronizer = await import("./Plugins/PluginConfigurationSynchronizer")
PluginConfigurationSynchronizer.activate(configuration, pluginManager)
const Achievements = await import("./Services/Learning/Achievements")
const achievements = Achievements.getInstance()
if (achievements) {
Debug.registerAchievements(achievements)
Sneak.registerAchievements(achievements)
Browser.registerAchievements(achievements)
}
Performance.endMeasure("Oni.Start.Activate")
checkForUpdates()
commandManager.registerCommand({
command: "oni.quit",
name: null,
detail: null,
execute: () => quit(),
})
Performance.endMeasure("Oni.Start")
ipcRenderer.send("Oni.started", "started")
_initializePromise.resolve()
}
const checkForUpdates = async (): Promise => {
const AutoUpdate = await import("./Services/AutoUpdate")
const { autoUpdater, constructFeedUrl } = AutoUpdate
const feedUrl = await constructFeedUrl("https://api.onivim.io/v1/update")
autoUpdater.onUpdateAvailable.subscribe(() => Log.info("Update available."))
autoUpdater.onUpdateNotAvailable.subscribe(() => Log.info("Update not available."))
autoUpdater.checkForUpdates(feedUrl)
}
================================================
FILE: browser/src/CSS.ts
================================================
/**
* CSS.ts
*
* Entry point for loading all of Oni's CSS
*/
export const activate = () => {
require("./UI/components/common.less") // tslint:disable-line no-var-requires
require("./overlay.less") // tslint:disable-line
require("./Services/Menu/Menu.less")
require("./UI/components/InstallHelp.less")
}
================================================
FILE: browser/src/Constants.ts
================================================
/**
* Constants.ts
*/
// Performance Constants
export namespace Delay {
export const INSTANT = 1
export const REAL_TIME = 10
export const NEAR_REAL_TIME = 50
export const NOT_REAL_TIME = 250
}
export namespace Vim {
export const MAX_VALUE = 2147483647
}
================================================
FILE: browser/src/Editor/BufferHighlights.ts
================================================
/**
* BufferHighlights.ts
*
* Helpers to manage buffer highlight state
*/
import * as SyntaxHighlighting from "./../Services/SyntaxHighlighting"
import { NeovimInstance } from "./../neovim"
// Line number to highlight src id, for clearing
export type BufferHighlightId = number
export interface IBufferHighlightsUpdater {
setHighlightsForLine(line: number, highlights: SyntaxHighlighting.HighlightInfo[]): void
clearHighlightsForLine(line: number): void
}
/**
* Helper class to efficiently update
* buffer highlights in a batch
*
* @name BufferHighlightsUpdater
* @class
*/
export class BufferHighlightsUpdater implements IBufferHighlightsUpdater {
private _calls: any[] = []
constructor(
private _bufferId: number,
private _neovimInstance: NeovimInstance,
private _highlightId: BufferHighlightId,
) {}
public async start(): Promise {
if (!this._highlightId) {
this._highlightId = await this._neovimInstance.request(
"nvim_buf_add_highlight",
[this._bufferId, 0, "", 0, 0, 0],
)
}
}
public setHighlightsForLine(
line: number,
highlights: SyntaxHighlighting.HighlightInfo[],
): void {
this.clearHighlightsForLine(line)
if (!highlights || !highlights.length) {
return
}
const addHighlightCalls = highlights.map(hl => {
const highlightGroup = this._neovimInstance.tokenColorSynchronizer.getHighlightGroupForTokenColor(
hl.tokenColor,
)
return [
"nvim_buf_add_highlight",
[
this._bufferId,
this._highlightId,
highlightGroup,
hl.range.start.line,
hl.range.start.character,
hl.range.end.character,
],
]
})
this._calls = this._calls.concat(addHighlightCalls)
}
public clearHighlightsForLine(line: number): void {
this._calls.push([
"nvim_buf_clear_highlight",
[this._bufferId, this._highlightId, line, line + 1],
])
}
public async apply(): Promise {
if (this._calls.length > 0) {
await this._neovimInstance.request("nvim_call_atomic", [this._calls])
}
return this._highlightId
}
}
================================================
FILE: browser/src/Editor/BufferManager.ts
================================================
/**
* BufferManager.ts
*
* Helpers to manage buffer state
*/
import * as os from "os"
import * as types from "vscode-languageserver-types"
import { Observable } from "rxjs/Observable"
import "rxjs/add/observable/defer"
import "rxjs/add/observable/from"
import "rxjs/add/operator/concatMap"
import { Store } from "redux"
import * as detectIndent from "detect-indent"
import * as Oni from "oni-api"
import * as Log from "oni-core-logging"
import {
BufferEventContext,
EventContext,
InactiveBufferContext,
NeovimInstance,
} from "./../neovim"
import * as LanguageManager from "./../Services/Language"
import { PromiseQueue } from "./../Services/Language/PromiseQueue"
import {
BufferHighlightId,
BufferHighlightsUpdater,
IBufferHighlightsUpdater,
} from "./BufferHighlights"
import * as Actions from "./NeovimEditor/NeovimEditorActions"
import * as State from "./NeovimEditor/NeovimEditorStore"
import * as Constants from "./../Constants"
import { TokenColor } from "./../Services/TokenColors"
import { IBufferLayer } from "./NeovimEditor/BufferLayerManager"
/**
* Candidate API methods
*/
export interface IBuffer extends Oni.Buffer {
tabstop: number
shiftwidth: number
comment: ICommentFormats
setLanguage(lang: string): Promise
getLayerById(id: string): T
getCursorPosition(): Promise
handleInput(key: string): boolean
detectIndentation(): Promise
setScratchBuffer(): Promise
}
type NvimError = [1, string]
interface ICommentFormats {
start: string
middle: string
end: string
defaults: string[]
}
const isStringArray = (value: NvimError | string[]): value is string[] => {
if (value && Array.isArray(value)) {
return typeof value[0] === "string"
}
return false
}
export type IndentationType = "tab" | "space"
export interface BufferIndentationInfo {
type: IndentationType
// If indentation is 'space', this is how
// many spaces are at a particular tabstop
amount: number
// String value for indentation
indent: string
}
const getStringFromTypeAndAmount = (type: IndentationType, amount: number): string => {
if (type === "tab") {
return "\t"
} else {
let str = ""
for (let i = 0; i < amount; i++) {
str += " "
}
return str
}
}
export class Buffer implements IBuffer {
private _id: string
private _filePath: string
private _language: string
private _cursor: Oni.Cursor
private _cursorOffset: number
private _version: number
private _modified: boolean
private _lineCount: number
private _tabstop: number
private _shiftwidth: number
private _comment: ICommentFormats
private _bufferHighlightId: BufferHighlightId = null
private _promiseQueue = new PromiseQueue()
public get shiftwidth(): number {
return this._shiftwidth
}
public get tabstop(): number {
return this._tabstop
}
public get comment(): ICommentFormats {
return this._comment
}
public get filePath(): string {
return this._filePath
}
public get language(): string {
return this._language
}
public get lineCount(): number {
return this._lineCount
}
public get cursor(): Oni.Cursor {
return this._cursor
}
public get cursorOffset(): number {
return this._cursorOffset
}
public get version(): number {
return this._version
}
public get modified(): boolean {
return this._modified
}
public get id(): string {
return this._id
}
constructor(
private _neovimInstance: NeovimInstance,
private _actions: typeof Actions,
private _store: Store,
evt: EventContext,
) {
this.updateFromEvent(evt)
}
public addLayer(layer: IBufferLayer): void {
this._actions.addBufferLayer(parseInt(this._id, 10), layer)
}
public getLayerById(id: string): T | null {
return (
((this._store
.getState()
.layers[parseInt(this._id, 10)].find(layer => layer.id === id) as any) as T) || null
)
}
public removeLayer(layer: IBufferLayer): void {
this._actions.removeBufferLayer(parseInt(this._id, 10), layer)
}
/**
* convertOffsetToLineColumn
*/
public async convertOffsetToLineColumn(
cursorOffset = this._cursorOffset,
): Promise {
const line: number = await this._neovimInstance.callFunction("byte2line", [cursorOffset])
const countFromLine: number = await this._neovimInstance.callFunction("line2byte", [line])
const column = cursorOffset - countFromLine
return types.Position.create(line - 1, column)
}
public async getCursorPosition(): Promise {
const pos = await this._neovimInstance.callFunction("getpos", ["."])
const [, oneBasedLine, oneBasedColumn] = pos
return types.Position.create(oneBasedLine - 1, oneBasedColumn - 1)
}
public async getLines(start?: number, end?: number): Promise {
if (typeof start !== "number") {
start = 0
}
if (typeof end !== "number") {
end = this._lineCount
}
if (end - start > 2500) {
Log.warn("getLines called with over 2500 lines, this may cause instability.")
}
// Neovim does not error if it is unable to get lines instead it returns an array
// of type [1, "an error message"] **on Some occasions**, we only check the first on the assumption that
// that is where the number is placed by neovim
const lines = await this._neovimInstance.request("nvim_buf_get_lines", [
parseInt(this._id, 10),
start,
end,
false,
])
if (isStringArray(lines)) {
return lines
}
return []
}
public async setLanguage(language: string): Promise {
this._language = language
await this._neovimInstance.command(`setl ft=${language}`)
}
public async setScratchBuffer(): Promise {
// set the open buffer to be a readonly throw away buffer, also add scrollbind
// may need a config option
const calls = [
["nvim_command", ["setlocal buftype=nofile"]],
["nvim_command", ["setlocal bufhidden=hide"]],
["nvim_command", ["setlocal noswapfile"]],
["nvim_command", ["setlocal nobuflisted"]],
["nvim_command", ["setlocal nomodifiable"]],
]
const [result, error] = await this._neovimInstance.request(
"nvim_call_atomic",
[calls],
)
if (typeof result === "number" && error) {
Log.info(`Failed to set scratch buffer due to ${error}`)
}
this._modified = false
}
public async detectIndentation(): Promise {
const bufferLines = await this.getLines(0, 1024)
const ret = detectIndent(bufferLines.join("\n"))
// We were able to infer tab settings from lines, so return
if (ret.type === "tab" || ret.type === "space") {
return ret
}
// Otherwise, we'll fall back to getting vim tab settings
const isSpaces = await this._neovimInstance.request("nvim_get_option", [
"expandtab",
])
const tabSize = await this._neovimInstance.request("nvim_get_option", ["tabstop"])
const tabType = isSpaces ? "space" : "tab"
return {
amount: tabSize,
type: tabType,
indent: getStringFromTypeAndAmount(tabType, tabSize),
}
}
public async applyTextEdits(textEdits: types.TextEdit | types.TextEdit[]): Promise {
const textEditsAsArray = textEdits instanceof Array ? textEdits : [textEdits]
const sortedEdits = LanguageManager.sortTextEdits(textEditsAsArray)
const deferredEdits = sortedEdits.map(te => {
return Observable.defer(async () => {
const range = te.range
Log.info("[Buffer] Applying edit")
const characterStart = range.start.character
const lineStart = range.start.line
const lineEnd = range.end.line
const characterEnd = range.end.character
const calls = []
calls.push(["nvim_command", ["silent! undojoin"]])
if (lineStart === lineEnd) {
const [lineContents] = await this.getLines(lineStart, lineStart + 1)
const beginning = lineContents.substring(0, range.start.character)
const end = lineContents.substring(range.end.character, lineContents.length)
const newLine = beginning + te.newText + end
const lines = newLine.split(os.EOL)
calls.push([
"nvim_buf_set_lines",
[parseInt(this._id, 10), lineStart, lineStart + 1, false, lines],
])
} else if (characterEnd === 0 && characterStart === 0) {
const lines = te.newText.split(os.EOL)
calls.push([
"nvim_buf_set_lines",
[parseInt(this._id, 10), lineStart, lineEnd, false, lines],
])
} else {
Log.warn("Multi-line mid character edits not currently supported")
}
await this._neovimInstance.request("nvim_call_atomic", [calls])
})
})
await Observable.from(deferredEdits)
.concatMap(de => de)
.toPromise()
}
public handleInput(key: string): boolean {
const state = this._store.getState()
const bufferLayers: IBufferLayer[] = state.layers[this._id]
if (!bufferLayers || !bufferLayers.length) {
return false
}
const layerShouldHandleInput = bufferLayers.reduce(
(layerHandlerExists, currentLayer) => {
if (layerHandlerExists) {
return true
}
if (!currentLayer || !currentLayer.handleInput) {
return false
} else if (currentLayer.isActive && currentLayer.isActive()) {
return currentLayer.handleInput(key)
}
return false
},
false,
)
return layerShouldHandleInput
}
public async updateHighlights(
tokenColors: TokenColor[],
updateFunction: (highlightsUpdater: IBufferHighlightsUpdater) => void,
): Promise {
this._promiseQueue.enqueuePromise(async () => {
const bufferId = parseInt(this._id, 10)
const bufferUpdater = new BufferHighlightsUpdater(
bufferId,
this._neovimInstance,
this._bufferHighlightId,
)
await this._neovimInstance.tokenColorSynchronizer.synchronizeTokenColors(tokenColors)
await bufferUpdater.start()
updateFunction(bufferUpdater)
this._bufferHighlightId = await bufferUpdater.apply()
})
}
public async setLines(start: number, end: number, lines: string[]): Promise {
return this._neovimInstance.request("nvim_buf_set_lines", [
parseInt(this._id, 10),
start,
end,
false,
lines,
])
}
public async setCursorPosition(row: number, column: number): Promise {
await this._neovimInstance.eval(`setpos(".", [${this._id}, ${row + 1}, ${column + 1}, 0])`)
}
public async getSelectionRange(): Promise {
const startRange = await this._neovimInstance.callFunction("getpos", ["'<'"])
const endRange = await this._neovimInstance.callFunction("getpos", ["'>"])
const [, startLine, startColumn] = startRange
let [, endLine, endColumn] = endRange
if (startLine === 0 && startColumn === 0 && endLine === 0 && endColumn === 0) {
return null
}
if (endColumn === Constants.Vim.MAX_VALUE) {
endLine++
endColumn = 1
}
return types.Range.create(startLine - 1, startColumn - 1, endLine - 1, endColumn - 1)
}
public async getTokenAt(line: number, column: number): Promise {
const result = await this.getLines(line, line + 1)
const tokenRegEx = LanguageManager.getInstance().getTokenRegex(this.language)
const getLastMatchingCharacter = (
lineContents: string,
character: number,
dir: number,
regex: RegExp,
) => {
while (character > 0 && character < lineContents.length) {
if (!lineContents[character].match(regex)) {
return character - dir
}
character += dir
}
return character
}
const getToken = (lineContents: string, character: number): Oni.IToken => {
if (!lineContents || !character) {
return null
}
const tokenStart = getLastMatchingCharacter(lineContents, character, -1, tokenRegEx)
const tokenEnd = getLastMatchingCharacter(lineContents, character, 1, tokenRegEx)
const range = types.Range.create(line, tokenStart, line, tokenEnd)
const tokenName = lineContents.substring(tokenStart, tokenEnd + 1)
return {
tokenName,
range,
}
}
return getToken(result[0], column)
}
public updateFromEvent(evt: EventContext): void {
this._id = evt.bufferNumber.toString()
this._filePath = evt.bufferFullPath
this._language = evt.filetype
this._version = evt.version
this._modified = evt.modified
this._lineCount = evt.bufferTotalLines
this._cursorOffset = evt.byte
this._tabstop = evt.tabstop
this._shiftwidth = evt.shiftwidth
this._comment = this.formatCommentOption(evt.comments)
this._cursor = {
line: evt.line - 1,
column: evt.column - 1,
}
}
public formatCommentOption(comments: string): ICommentFormats {
if (!comments) {
return null
}
try {
const commentsArray = comments.split(",")
const commentFormats = commentsArray.reduce(
(acc, str) => {
const [flag, character] = str.split(":")
switch (true) {
case flag.includes("s"):
acc.start = character
return acc
case flag.includes("m"):
acc.middle = character
return acc
case flag.includes("e"):
acc.end = character
return acc
default:
acc.defaults.push(character)
return acc
}
},
{
start: null,
middle: null,
end: null,
defaults: [],
},
)
return commentFormats
} catch (e) {
Log.warn(`Error formatting neovim comment options due to ${e.message}`)
return null
}
}
}
// Helper for managing buffer state
export class BufferManager {
private _idToBuffer: { [id: string]: Buffer } = {}
private _filePathToId: { [filePath: string]: string } = {}
private _bufferList: { [id: string]: InactiveBuffer } = {}
constructor(
private _neovimInstance: NeovimInstance,
private _actions: typeof Actions,
private _store: Store,
) {}
public updateBufferFromEvent(evt: EventContext): Buffer {
const id = evt.bufferNumber.toString()
const currentBuffer = this.getBufferById(id)
if (evt.bufferFullPath) {
this._filePathToId[evt.bufferFullPath] = id
}
if (currentBuffer) {
currentBuffer.updateFromEvent(evt)
} else {
const buf = new Buffer(this._neovimInstance, this._actions, this._store, evt)
this._idToBuffer[id] = buf
}
return this._idToBuffer[id]
}
public populateBufferList(buffers: BufferEventContext): void {
const bufferlist = buffers.existingBuffers.reduce((list, buffer) => {
const id = `${buffer.bufferNumber}`
if (buffer.bufferFullPath) {
this._filePathToId[buffer.bufferFullPath] = id
list[id] = new InactiveBuffer(buffer)
}
return list
}, {})
const currentId = buffers.current.bufferNumber.toString()
const current = this.getBufferById(currentId)
this._bufferList = { ...bufferlist, [currentId]: current }
}
public getBufferById(id: string): Buffer {
return this._idToBuffer[id]
}
public getBuffers(): Array {
return Object.values(this._bufferList)
}
}
export class InactiveBuffer implements Oni.InactiveBuffer {
private _id: string
private _filePath: string
private _language: string
private _version: number
private _modified: boolean
private _lineCount: number
public get id(): string {
return this._id
}
public get filePath(): string {
return this._filePath
}
public get language(): string {
return this._language
}
public get version(): number {
return this._version
}
public get modified(): boolean {
return this._modified
}
public get lineCount(): number {
return this._lineCount
}
constructor(inactiveBuffer: InactiveBufferContext) {
this._id = `${inactiveBuffer.bufferNumber}`
this._filePath = inactiveBuffer.bufferFullPath
this._language = inactiveBuffer.filetype
this._version = inactiveBuffer.version || null
this._modified = inactiveBuffer.modified || false
this._lineCount = null
}
}
================================================
FILE: browser/src/Editor/Editor.ts
================================================
/**
* Interface that describes an Editor -
* an editor handles rendering and input
* for a specific window.
*/
import * as Oni from "oni-api"
import { Event, IEvent } from "oni-types"
import * as types from "vscode-languageserver-types"
import { Disposable } from "./../Utility"
/**
* Base class for Editor implementations
*/
export abstract class Editor extends Disposable implements Oni.Editor {
private _currentMode: string
private _onBufferEnterEvent = new Event()
private _onBufferLeaveEvent = new Event()
private _onBufferChangedEvent = new Event()
private _onBufferSavedEvent = new Event()
private _onBufferScrolledEvent = new Event()
private _onCursorMoved = new Event()
private _onModeChangedEvent = new Event()
public get mode(): string {
return this._currentMode
}
public get activeBuffer(): Oni.Buffer {
return null
}
public get onCursorMoved(): IEvent {
return this._onCursorMoved
}
public abstract init(filesToOpen: string[]): void
// Events
public get onModeChanged(): IEvent {
return this._onModeChangedEvent
}
public get onBufferEnter(): IEvent {
return this._onBufferEnterEvent
}
public get onBufferLeave(): IEvent {
return this._onBufferLeaveEvent
}
public get onBufferChanged(): IEvent {
return this._onBufferChangedEvent
}
public get onBufferSaved(): IEvent {
return this._onBufferSavedEvent
}
public get onBufferScrolled(): IEvent {
return this._onBufferScrolledEvent
}
public getBuffers(): Array {
return []
}
public /* virtual */ openFile(
filePath: string,
openOptions: Oni.FileOpenOptions = Oni.DefaultFileOpenOptions,
): Promise {
return Promise.reject("Not implemented")
}
public async blockInput(
inputFunction: (input: Oni.InputCallbackFunction) => Promise,
): Promise {
return Promise.reject("Not implemented")
}
public setTextOptions(options: Oni.EditorTextOptions): Promise {
return Promise.reject("Not implemented")
}
public abstract render(): JSX.Element
public abstract setSelection(selectionRange: types.Range): Promise
protected setMode(mode: Oni.Vim.Mode): void {
if (mode !== this._currentMode) {
this._currentMode = mode
this._onModeChangedEvent.dispatch(mode)
}
}
protected notifyCursorMoved(cursor: Oni.Cursor): void {
this._onCursorMoved.dispatch(cursor)
}
protected notifyBufferChanged(bufferChangeEvent: Oni.EditorBufferChangedEventArgs): void {
this._onBufferChangedEvent.dispatch(bufferChangeEvent)
}
protected notifyBufferEnter(bufferEvent: Oni.EditorBufferEventArgs): void {
this._onBufferEnterEvent.dispatch(bufferEvent)
}
protected notifyBufferLeave(bufferEvent: Oni.EditorBufferEventArgs): void {
this._onBufferLeaveEvent.dispatch(bufferEvent)
}
protected notifyBufferSaved(bufferEvent: Oni.EditorBufferEventArgs): void {
this._onBufferSavedEvent.dispatch(bufferEvent)
}
protected notifyBufferScrolled(bufferScrollEvent: Oni.EditorBufferScrolledEventArgs): void {
this._onBufferScrolledEvent.dispatch(bufferScrollEvent)
}
}
================================================
FILE: browser/src/Editor/NeovimEditor/BufferLayerManager.ts
================================================
/**
* BufferLayerManager.ts
*
* BufferLayerManager tracks the lifecycle of 'buffer layers'
*/
import * as Oni from "oni-api"
export type BufferLayerFactory = (buf: Oni.Buffer) => Oni.BufferLayer
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 => {
if (!language || language === "*") {
return true
} else {
return buf.language === language
}
}
export interface BufferLayerInfo {
filter: BufferFilter
layerFactory: BufferLayerFactory
}
export class BufferLayerManager {
private _layers: BufferLayerInfo[] = []
private _buffers: Oni.Buffer[] = []
public addBufferLayer(
filterOrLanguage: BufferFilter | string,
layerFactory: BufferLayerFactory,
) {
const filter: BufferFilter =
typeof filterOrLanguage === "string"
? createBufferFilterFromLanguage(filterOrLanguage)
: filterOrLanguage
this._layers.push({
filter,
layerFactory,
})
this._buffers.forEach(buf => {
if (filter(buf)) {
buf.addLayer(layerFactory(buf))
}
})
}
public notifyBufferEnter(buf: Oni.Buffer): void {
if (this._buffers.indexOf(buf) === -1) {
this._buffers.push(buf)
this._layers.forEach(layerInfo => {
if (layerInfo.filter(buf)) {
buf.addLayer(layerInfo.layerFactory(buf))
}
})
}
}
public notifyBufferFileTypeChanged(buf: Oni.Buffer): void {
this._buffers = this._buffers.filter(b => b.id !== buf.id)
this.notifyBufferEnter(buf)
}
}
const getInstance = (() => {
const instance = new BufferLayerManager()
return () => instance
})()
export default getInstance
export const wrapReactComponentWithLayer = (
id: string,
component: JSX.Element,
): Oni.BufferLayer => {
return {
id,
render: (context: Oni.BufferLayerRenderContext) => (context.isActive ? component : null),
}
}
================================================
FILE: browser/src/Editor/NeovimEditor/CompletionMenu.ts
================================================
/**
* CompletionMenu.ts
*
* This is the completion menu that integrates with the completion providers
* (which is primarily language server right now)
* It's really just glue between the ContextMenu and Completion store.
*/
import * as types from "vscode-languageserver-types"
import { Event, IEvent } from "oni-types"
import { getDocumentationText } from "../../Plugins/Api/LanguageClient/LanguageClientHelpers"
import { ContextMenu } from "./../../Services/ContextMenu"
import * as CompletionUtility from "./../../Services/Completion/CompletionUtility"
export class CompletionMenu {
private _onItemFocusedEvent: Event = new Event()
private _onItemSelectedEvent: Event = new Event()
public get onItemFocused(): IEvent {
return this._onItemFocusedEvent
}
public get onItemSelected(): IEvent {
return this._onItemSelectedEvent
}
constructor(private _contextMenu: ContextMenu) {
this._contextMenu.onSelectedItemChanged.subscribe(item =>
this._onItemFocusedEvent.dispatch(item.rawCompletion),
)
this._contextMenu.onItemSelected.subscribe(item =>
this._onItemSelectedEvent.dispatch(item.rawCompletion),
)
}
public show(options: types.CompletionItem[], filterText: string): void {
const menuOptions = options.map(_convertCompletionForContextMenu)
if (this._contextMenu.isOpen()) {
this._contextMenu.setItems(menuOptions)
this._contextMenu.setFilter(filterText)
} else {
this._contextMenu.show(menuOptions, filterText)
}
}
public hide(): void {
this._contextMenu.hide()
}
}
// TODO: Should this be moved to another level? Like over to the menu renderer?
// It'd be nice if this layer only cared about `types.CompletionItem` and didn't
// have to worry about presentational aspects..
const _convertCompletionForContextMenu = (completion: types.CompletionItem): any => ({
label: completion.label,
detail: completion.detail,
documentation: getCompletionDocumentation(completion),
icon: CompletionUtility.convertKindToIconName(completion.kind),
rawCompletion: completion,
})
const getCompletionDocumentation = (item: types.CompletionItem): string | null => {
if (item.documentation) {
return getDocumentationText(item.documentation)
} else if (item.data && item.data.documentation) {
return item.data.documentation
} else {
return null
}
}
================================================
FILE: browser/src/Editor/NeovimEditor/Definition.ts
================================================
/**
* Definition.ts
*/
import { Store } from "redux"
import * as Oni from "oni-api"
import * as Helpers from "./../../Plugins/Api/LanguageClient/LanguageClientHelpers"
import * as State from "./NeovimEditorStore"
export enum OpenType {
NewTab = 0,
SplitVertical = 1,
SplitHorizontal = 2,
}
export class Definition {
constructor(private _editor: Oni.Editor, private _store: Store) {}
public async gotoDefinitionUnderCursor(openOptions?: Oni.FileOpenOptions): Promise {
const activeDefinition = this._store.getState().definition
if (!activeDefinition) {
return
}
const { uri, range } = activeDefinition.definitionLocation
const line = range.start.line
const column = range.start.character
await this.gotoPositionInUri(uri, line, column, openOptions)
}
public async gotoPositionInUri(
uri: string,
line: number,
column: number,
openOptions?: Oni.FileOpenOptions,
): Promise {
const filePath = Helpers.unwrapFileUriPath(uri)
const activeEditor = this._editor
await this._editor.openFile(filePath, openOptions)
await activeEditor.neovim.command(`cal cursor(${line + 1}, ${column + 1})`)
await activeEditor.neovim.command("norm zz")
}
}
================================================
FILE: browser/src/Editor/NeovimEditor/FileDropHandler.tsx
================================================
import * as React from "react"
type SetRef = (elem: HTMLElement) => void
interface IFileDropHandler {
handleFiles: (files: FileList) => void
children: (args: { setRef: SetRef }) => React.ReactElement<{ setRef: SetRef }>
}
type DragTypeName = "ondragover" | "ondragleave" | "ondragenter"
/**
* Gets a target element via a callback ref and attaches a file drop event listener callback
* N.B. the element cannot be obscured as this will prevent event transmission
* @name FileDropHandler
* @function
*
* @extends {React}
*/
export default class FileDropHandler extends React.Component {
private _target: HTMLElement
public componentDidMount() {
this.addDropHandler()
}
public setRef = (element: HTMLElement) => {
this._target = element
}
public addDropHandler() {
if (!this._target) {
return
}
const dragTypes = ["ondragenter", "ondragover", "ondragleave"]
dragTypes.map((event: DragTypeName) => {
if (this._target[event]) {
this._target[event] = ev => {
ev.preventDefault()
ev.stopPropagation()
}
}
})
this._target.ondrop = async ev => {
const { files } = ev.dataTransfer
if (files.length) {
await this.props.handleFiles(files)
}
ev.preventDefault()
}
}
public render() {
return this.props.children({ setRef: this.setRef })
}
}
================================================
FILE: browser/src/Editor/NeovimEditor/HoverRenderer.tsx
================================================
/**
* Hover.tsx
*/
import * as Oni from "oni-api"
import * as os from "os"
import * as React from "react"
import * as types from "vscode-languageserver-types"
import getTokens from "./../../Services/SyntaxHighlighting/TokenGenerator"
import styled, { enableMouse } from "./../../UI/components/common"
import { ErrorInfo } from "./../../UI/components/ErrorInfo"
import { QuickInfoElement } from "./../../UI/components/QuickInfo"
import QuickInfoWithTheme from "./../../UI/components/QuickInfoContainer"
import * as Helpers from "./../../Plugins/Api/LanguageClient/LanguageClientHelpers"
import { Configuration } from "./../../Services/Configuration"
import { convertMarkdown } from "./markdown"
import { IToolTipsProvider } from "./ToolTipsProvider"
const HoverToolTipId = "hover-tool-tip"
const HoverRendererContainer = styled.div`
user-select: none;
cursor: default;
${enableMouse};
`
export class HoverRenderer {
constructor(
private _editor: Oni.Editor,
private _configuration: Configuration,
private _toolTipsProvider: IToolTipsProvider,
) {}
public async showQuickInfo(
x: number,
y: number,
hover: types.Hover,
errors: types.Diagnostic[],
): Promise {
const elem = await this._renderQuickInfoElement(hover, errors)
if (!elem) {
return
}
this._toolTipsProvider.showToolTip(HoverToolTipId, elem, {
position: { pixelX: x, pixelY: y },
openDirection: 1,
padding: "0px",
})
}
public hideQuickInfo(): void {
this._toolTipsProvider.hideToolTip(HoverToolTipId)
}
private async _renderQuickInfoElement(
hover: types.Hover,
errors: types.Diagnostic[],
): Promise {
const titleAndContents = await getTitleAndContents(hover)
const showDebugScope = this._configuration.getValue(
"editor.textMateHighlighting.debugScopes",
)
const errorsExist = Boolean(errors && errors.length)
const contentExists = Boolean(errorsExist || titleAndContents || showDebugScope)
return (
contentExists && (
{showDebugScope && this._getDebugScopesElement()}
)
)
}
private _getDebugScopesElement(): JSX.Element {
const editor: any = this._editor
if (!editor || !editor.syntaxHighlighter) {
return null
}
const cursor = editor.activeBuffer.cursor
const scopeInfo = editor.syntaxHighlighter.getHighlightTokenAt(editor.activeBuffer.id, {
line: cursor.line,
character: cursor.column,
})
if (!scopeInfo || !scopeInfo.scopes) {
return null
}
const items = scopeInfo.scopes.map((si: string) => {si})
return (
)
}
}
const html = (str: string) => ({ __html: str })
interface ErrorElementProps {
errors: types.Diagnostic[]
hasQuickInfo: boolean
isVisible: boolean
}
const ErrorElement = ({ isVisible, errors, hasQuickInfo }: ErrorElementProps) => {
return (
isVisible && (
)
)
}
const getTitleAndContents = async (result: types.Hover) => {
if (!result || !result.contents) {
return null
}
const contents = Helpers.getTextFromContents(result.contents)
if (!contents.length) {
return null
}
const [{ value: titleContent, language }, ...remaining] = contents
if (!titleContent) {
return null
}
const remainder = remaining.map(r => r.value)
const [hasRemainder] = remainder
if (!hasRemainder) {
const tokensPerLine = await getTokens({ language, line: titleContent })
return {
title: html(convertMarkdown({ markdown: titleContent, tokens: tokensPerLine })),
description: null,
}
} else {
const descriptionContent = remainder.join(os.EOL)
const tokensPerLine = await getTokens({ language, line: titleContent })
return {
title: html(convertMarkdown({ markdown: titleContent, tokens: tokensPerLine })),
description: html(
convertMarkdown({
markdown: descriptionContent,
type: "documentation",
}),
),
}
}
}
================================================
FILE: browser/src/Editor/NeovimEditor/NeovimActiveWindow.tsx
================================================
/**
* ActiveWindow.tsx
*
* Helper component that is always sized and positioned around the currently
* active window in Neovim.
*/
import * as React from "react"
export interface IActiveWindowProps {
pixelX: number
pixelY: number
pixelWidth: number
pixelHeight: number
}
export class NeovimActiveWindow extends React.PureComponent {
public render(): JSX.Element {
const px = (str: number): string => `${str}px`
const style: React.CSSProperties = {
position: "absolute",
left: px(this.props.pixelX),
top: px(this.props.pixelY),
width: px(this.props.pixelWidth),
height: px(this.props.pixelHeight),
overflowY: "hidden",
overflowX: "hidden",
}
return {this.props.children}
}
}
================================================
FILE: browser/src/Editor/NeovimEditor/NeovimBufferLayersView.tsx
================================================
/**
* NeovimLayersView.tsx
*
* Renders layers above vim windows
*/
import * as React from "react"
import { connect } from "react-redux"
import { createSelector } from "reselect"
import * as Oni from "oni-api"
import { NeovimActiveWindow } from "./NeovimActiveWindow"
import * as State from "./NeovimEditorStore"
import styled, { StackLayer } from "../../UI/components/common"
export interface NeovimBufferLayersViewProps {
activeWindowId: number
windows: State.IWindow[]
layers: State.Layers
fontPixelWidth: number
fontPixelHeight: number
}
const InnerLayer = styled.div`
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
overflow: hidden;
`
export interface LayerContextWithCursor extends Oni.BufferLayerRenderContext {
cursorLine: number
cursorColumn: number
}
export class NeovimBufferLayersView extends React.PureComponent {
public render(): JSX.Element {
const containers = this.props.windows.map(windowState => {
const layers: Oni.BufferLayer[] = this.props.layers[windowState.bufferId] || []
const layerContext: LayerContextWithCursor = {
isActive: windowState.windowId === this.props.activeWindowId,
windowId: windowState.windowId,
fontPixelWidth: this.props.fontPixelWidth,
fontPixelHeight: this.props.fontPixelHeight,
bufferToScreen: windowState.bufferToScreen,
screenToPixel: windowState.screenToPixel,
bufferToPixel: windowState.bufferToPixel,
dimensions: windowState.dimensions,
visibleLines: windowState.visibleLines,
topBufferLine: windowState.topBufferLine,
bottomBufferLine: windowState.bottomBufferLine,
cursorColumn: windowState.column,
cursorLine: windowState.line,
}
const layerElements = layers.map(layer => {
return (
{layer.render(layerContext)}
)
})
const dimensions = getWindowPixelDimensions(windowState)
return (
{layerElements}
)
})
return {containers}
}
}
const EmptySize = {
pixelX: -1,
pixelY: -1,
pixelWidth: 0,
pixelHeight: 0,
}
const getWindowPixelDimensions = (win: State.IWindow) => {
if (!win || !win.screenToPixel) {
return EmptySize
}
const start = win.screenToPixel({
screenX: win.dimensions.x,
screenY: win.dimensions.y,
})
const size = win.screenToPixel({
screenX: win.dimensions.width,
screenY: win.dimensions.height,
})
return {
pixelX: start.pixelX,
pixelY: start.pixelY - 1,
pixelWidth: size.pixelX,
pixelHeight: size.pixelY + 2,
}
}
const EmptyState: NeovimBufferLayersViewProps = {
activeWindowId: -1,
layers: {},
windows: [],
fontPixelHeight: -1,
fontPixelWidth: -1,
}
const getActiveVimTabPage = (state: State.IState) => state.activeVimTabPage
const getWindowState = (state: State.IState) => state.windowState
const windowSelector = createSelector(
[getActiveVimTabPage, getWindowState],
(tabPage: State.IVimTabPage, windowState: State.IWindowState) => {
const windows = tabPage.windowIds.map(windowId => {
return windowState.windows[windowId]
})
return windows.sort((a, b) => a.windowId - b.windowId)
},
)
const mapStateToProps = (state: State.IState): NeovimBufferLayersViewProps => {
if (!state.activeVimTabPage) {
return EmptyState
}
const windows = windowSelector(state)
return {
activeWindowId: state.windowState.activeWindow,
windows,
layers: state.layers,
fontPixelWidth: state.fontPixelWidth,
fontPixelHeight: state.fontPixelHeight,
}
}
export const NeovimBufferLayers = connect(mapStateToProps)(NeovimBufferLayersView)
================================================
FILE: browser/src/Editor/NeovimEditor/NeovimEditor.tsx
================================================
/**
* NeovimEditor.ts
*
* Editor implementation for Neovim
*/
import * as os from "os"
import * as React from "react"
import "rxjs/add/observable/defer"
import "rxjs/add/observable/merge"
import "rxjs/add/operator/map"
import "rxjs/add/operator/mergeMap"
import { Observable } from "rxjs/Observable"
import * as types from "vscode-languageserver-types"
import { Provider } from "react-redux"
import { bindActionCreators, Store } from "redux"
import { clipboard, ipcRenderer } from "electron"
import * as Oni from "oni-api"
import * as Log from "oni-core-logging"
import { Event, IEvent } from "oni-types"
import { addDefaultUnitIfNeeded } from "./../../Font"
import {
BufferEventContext,
EventContext,
INeovimStartOptions,
NeovimInstance,
NeovimScreen,
NeovimWindowManager,
ScreenWithPredictions,
} from "./../../neovim"
import { INeovimRenderer } from "./../../Renderer"
import { PluginManager } from "./../../Plugins/PluginManager"
import { IColors } from "./../../Services/Colors"
import { commandManager } from "./../../Services/CommandManager"
import { Completion, CompletionProviders } from "./../../Services/Completion"
import { Configuration, IConfigurationValues } from "./../../Services/Configuration"
import { IDiagnosticsDataSource } from "./../../Services/Diagnostics"
import { Overlay, OverlayManager } from "./../../Services/Overlay"
import { ISession } from "./../../Services/Sessions"
import { SnippetManager } from "./../../Services/Snippets"
import { TokenColors } from "./../../Services/TokenColors"
import * as Shell from "./../../UI/Shell"
import {
addInsertModeLanguageFunctionality,
LanguageEditorIntegration,
LanguageManager,
} from "./../../Services/Language"
import {
ISyntaxHighlighter,
NullSyntaxHighlighter,
SyntaxHighlighter,
} from "./../../Services/SyntaxHighlighting"
import { MenuManager } from "./../../Services/Menu"
import { IThemeMetadata, ThemeManager } from "./../../Services/Themes"
import { TypingPredictionManager } from "./../../Services/TypingPredictionManager"
import { Workspace } from "./../../Services/Workspace"
import { Editor } from "./../Editor"
import { BufferManager } from "./../BufferManager"
import { CompletionMenu } from "./CompletionMenu"
import { HoverRenderer } from "./HoverRenderer"
import { NeovimPopupMenu } from "./NeovimPopupMenu"
import NeovimSurface from "./NeovimSurface"
import { ContextMenuManager } from "./../../Services/ContextMenu"
import { asObservable, normalizePath, sleep } from "./../../Utility"
import * as VimConfigurationSynchronizer from "./../../Services/VimConfigurationSynchronizer"
import getLayerManagerInstance from "./BufferLayerManager"
import { Definition } from "./Definition"
import * as ActionCreators from "./NeovimEditorActions"
import { NeovimEditorCommands } from "./NeovimEditorCommands"
import { createStore, IState, ITab } from "./NeovimEditorStore"
import { Rename } from "./Rename"
import { Symbols } from "./Symbols"
import { IToolTipsProvider, NeovimEditorToolTipsProvider } from "./ToolTipsProvider"
import CommandLine from "./../../UI/components/CommandLine"
import ExternalMenus from "./../../UI/components/ExternalMenus"
import WildMenu from "./../../UI/components/WildMenu"
import { CanvasRenderer, WebGLRenderer } from "../../Renderer"
import { getInstance as getNotificationsInstance } from "./../../Services/Notifications"
type NeovimError = [number, string]
export class NeovimEditor extends Editor implements Oni.Editor {
private _bufferManager: BufferManager
private _neovimInstance: NeovimInstance
private _renderer: INeovimRenderer
private _screen: NeovimScreen
private _completionMenu: CompletionMenu
private _contextMenuManager: ContextMenuManager
private _popupMenu: NeovimPopupMenu
private _errorInitializing: boolean = false
private _store: Store
private _actions: typeof ActionCreators
private _pendingAnimationFrame: boolean = false
private _onEnterEvent: Event = new Event()
private _modeChanged$: Observable
private _cursorMoved$: Observable
private _cursorMovedI$: Observable
private _onScroll$: Observable
private _hasLoaded: boolean = false
private _windowManager: NeovimWindowManager
private _currentColorScheme: string = ""
private _currentBackground: string = ""
private _isFirstRender: boolean = true
private _lastBufferId: string = null
private _typingPredictionManager: TypingPredictionManager = new TypingPredictionManager()
private _syntaxHighlighter: ISyntaxHighlighter
private _languageIntegration: LanguageEditorIntegration
private _completion: Completion
private _hoverRenderer: HoverRenderer
private _rename: Rename = null
private _symbols: Symbols = null
private _definition: Definition = null
private _toolTipsProvider: IToolTipsProvider
private _commands: NeovimEditorCommands
private _externalMenuOverlay: Overlay
private _bufferLayerManager = getLayerManagerInstance()
private _screenWithPredictions: ScreenWithPredictions
private _onShowWelcomeScreen = new Event()
private _onNeovimQuit: Event = new Event()
private _autoFocus: boolean = true
public get onNeovimQuit(): IEvent {
return this._onNeovimQuit
}
public get onShowWelcomeScreen() {
return this._onShowWelcomeScreen
}
public get /* override */ activeBuffer(): Oni.Buffer {
return this._bufferManager.getBufferById(this._lastBufferId)
}
// Capabilities
public get neovim(): Oni.NeovimEditorCapability {
return this._neovimInstance
}
public get bufferLayers() {
return this._bufferLayerManager
}
/**
* Gets whether or not the editor should autoFocus,
* meaning, grab focus on first mount
*/
public get autoFocus(): boolean {
return this._autoFocus
}
public set autoFocus(val: boolean) {
this._autoFocus = val
}
public get syntaxHighlighter(): ISyntaxHighlighter {
return this._syntaxHighlighter
}
constructor(
private _colors: IColors,
private _completionProviders: CompletionProviders,
private _configuration: Configuration,
private _diagnostics: IDiagnosticsDataSource,
private _languageManager: LanguageManager,
private _menuManager: MenuManager,
private _overlayManager: OverlayManager,
private _pluginManager: PluginManager,
private _snippetManager: SnippetManager,
private _themeManager: ThemeManager,
private _tokenColors: TokenColors,
private _workspace: Workspace,
) {
super()
this._store = createStore()
this._actions = bindActionCreators(ActionCreators as any, this._store.dispatch)
this._toolTipsProvider = new NeovimEditorToolTipsProvider(this._actions)
this._contextMenuManager = new ContextMenuManager(this._toolTipsProvider)
this._neovimInstance = new NeovimInstance(100, 100, this._configuration)
this._bufferManager = new BufferManager(this._neovimInstance, this._actions, this._store)
this._screen = new NeovimScreen()
this._screenWithPredictions = new ScreenWithPredictions(this._screen, this._configuration)
this._hoverRenderer = new HoverRenderer(this, this._configuration, this._toolTipsProvider)
this._definition = new Definition(this, this._store)
this._symbols = new Symbols(
this,
this._definition,
this._languageManager,
this._menuManager,
)
this._diagnostics.onErrorsChanged.subscribe(() => {
const errors = this._diagnostics.getErrors()
this._actions.setErrors(errors)
})
this._externalMenuOverlay = this._overlayManager.createItem()
this._externalMenuOverlay.setContents(
,
)
this._popupMenu = new NeovimPopupMenu(
this._neovimInstance.onShowPopupMenu,
this._neovimInstance.onHidePopupMenu,
this._neovimInstance.onSelectPopupMenu,
this.onBufferEnter,
this._toolTipsProvider,
)
const notificationManager = getNotificationsInstance()
this._neovimInstance.onMessage.subscribe(messageInfo => {
// TODO: Hook up real notifications
const notification = notificationManager.createItem()
notification.setLevel("error")
notification.setContents(messageInfo.title, messageInfo.details)
notification.onClick.subscribe(() =>
commandManager.executeCommand("oni.config.openInitVim"),
)
notification.show()
})
const initVimPath = this._neovimInstance.doesInitVimExist()
const initVimInUse = this._configuration.getValue("oni.loadInitVim")
const hasCheckedInitVim = this._configuration.getValue("_internal.hasCheckedInitVim")
if (initVimPath && !initVimInUse && !hasCheckedInitVim) {
const initVimNotification = notificationManager.createItem()
initVimNotification.setLevel("info")
initVimNotification.setContents(
"init.vim found",
`We found an init.vim file would you like Oni to use it?
This will result in Oni being reloaded`,
)
initVimNotification.setButtons([
{
title: "Yes",
callback: () => {
this._configuration.setValues(
{ "_internal.hasCheckedInitVim": true, "oni.loadInitVim": true },
true,
)
commandManager.executeCommand("oni.debug.reload")
},
},
{
title: "No",
callback: () => {
this._configuration.setValues(
{ "oni.loadInitVim": false, "_internal.hasCheckedInitVim": true },
true,
)
},
},
])
initVimNotification.show()
}
const ligaturesEnabled = this._configuration.getValue("editor.fontLigatures")
this._renderer =
this._configuration.getValue("editor.renderer") === "webgl"
? new WebGLRenderer(ligaturesEnabled)
: new CanvasRenderer()
this._rename = new Rename(
this,
this._languageManager,
this._toolTipsProvider,
this._workspace,
)
// Services
const onColorsChanged = () => {
const updatedColors = this._colors.getColors()
this._actions.setColors(updatedColors)
}
this._colors.onColorsChanged.subscribe(onColorsChanged)
onColorsChanged()
this.trackDisposable(
this._tokenColors.onTokenColorsChanged.subscribe(() => {
if (this._neovimInstance.isInitialized) {
this._syntaxHighlighter.notifyColorschemeRedraw(`${this.activeBuffer.id}`)
}
}),
)
// Overlays
// TODO: Replace `OverlayManagement` concept and associated window management code with
// explicit window management: #362
this._windowManager = new NeovimWindowManager(this._neovimInstance)
this.trackDisposable(
this._neovimInstance.onCommandLineShow.subscribe(showCommandLineInfo => {
this._actions.showCommandLine(
showCommandLineInfo.content,
showCommandLineInfo.pos,
showCommandLineInfo.firstc,
showCommandLineInfo.prompt,
showCommandLineInfo.indent,
showCommandLineInfo.level,
)
this._externalMenuOverlay.show()
}),
)
this.trackDisposable(
this._neovimInstance.onWildMenuShow.subscribe(wildMenuInfo => {
this._actions.showWildMenu(wildMenuInfo)
}),
)
this.trackDisposable(
this._neovimInstance.onWildMenuSelect.subscribe(wildMenuInfo => {
this._actions.wildMenuSelect(wildMenuInfo)
}),
)
this.trackDisposable(
this._neovimInstance.onWildMenuHide.subscribe(() => {
this._actions.hideWildMenu()
}),
)
this.trackDisposable(
this._neovimInstance.onCommandLineHide.subscribe(() => {
this._actions.hideCommandLine()
this._externalMenuOverlay.hide()
}),
)
this.trackDisposable(
this._neovimInstance.onCommandLineSetCursorPosition.subscribe(commandLinePos => {
this._actions.setCommandLinePosition(commandLinePos)
}),
)
this.trackDisposable(
this._windowManager.onWindowStateChanged.subscribe(tabPageState => {
if (!tabPageState) {
return
}
const filteredTabState = tabPageState.inactiveWindows.filter(w => !!w)
const inactiveIds = filteredTabState.map(w => w.windowNumber)
this._actions.setActiveVimTabPage(tabPageState.tabId, [
tabPageState.activeWindow.windowNumber,
...inactiveIds,
])
const { activeWindow } = tabPageState
if (activeWindow) {
this._actions.setWindowState(
activeWindow.windowNumber,
activeWindow.bufferId,
activeWindow.bufferFullPath,
activeWindow.column,
activeWindow.line,
activeWindow.bottomBufferLine,
activeWindow.topBufferLine,
activeWindow.dimensions,
activeWindow.bufferToScreen,
activeWindow.visibleLines,
)
}
filteredTabState.map(w => {
this._actions.setInactiveWindowState(w.windowNumber, w.dimensions)
})
}),
)
this.trackDisposable(
this._neovimInstance.onYank.subscribe(yankInfo => {
if (this._configuration.getValue("editor.clipboard.enabled")) {
const isYankAndAllowed =
yankInfo.operator === "y" &&
this._configuration.getValue("editor.clipboard.synchronizeYank")
const isDeleteAndAllowed =
yankInfo.operator === "d" &&
this._configuration.getValue("editor.clipboard.synchronizeDelete")
const isAllowed = isYankAndAllowed || isDeleteAndAllowed
if (isAllowed) {
const content = yankInfo.regcontents.join(os.EOL)
const postfix = yankInfo.regtype === "V" ? os.EOL : ""
clipboard.writeText(content + postfix)
}
}
}),
)
this.trackDisposable(
this._neovimInstance.onTitleChanged.subscribe(newTitle => {
const title = newTitle.replace(" - NVIM", " - ONI")
Shell.Actions.setWindowTitle(title)
}),
)
this.trackDisposable(
this._neovimInstance.onLeave.subscribe(() => {
this._onNeovimQuit.dispatch()
}),
)
this.trackDisposable(
this._neovimInstance.onOniCommand.subscribe(context => {
const commandToExecute = context.command
const commandArgs = context.args
commandManager.executeCommand(commandToExecute, commandArgs)
}),
)
// TODO: Refactor to event and track disposable
this.trackDisposable(
this._neovimInstance.onVimEvent.subscribe(evt => {
if (evt.eventName !== "VimLeave") {
this._updateWindow(evt.eventContext)
this._bufferManager.updateBufferFromEvent(evt.eventContext)
}
}),
)
this.trackDisposable(
this._neovimInstance.autoCommands.onBufDelete.subscribe((evt: BufferEventContext) =>
this._onBufDelete(evt),
),
)
this.trackDisposable(
this._neovimInstance.autoCommands.onBufUnload.subscribe((evt: BufferEventContext) =>
this._onBufUnload(evt),
),
)
this.trackDisposable(
this._neovimInstance.autoCommands.onBufEnter.subscribe((evt: BufferEventContext) =>
this._onBufEnter(evt),
),
)
this.trackDisposable(
this._neovimInstance.autoCommands.onBufWinEnter.subscribe((evt: BufferEventContext) =>
this._onBufEnter(evt),
),
)
this.trackDisposable(
this._neovimInstance.autoCommands.onFileTypeChanged.subscribe((evt: EventContext) =>
this._onFileTypeChanged(evt),
),
)
this.trackDisposable(
this._neovimInstance.autoCommands.onBufWipeout.subscribe((evt: BufferEventContext) =>
this._onBufWipeout(evt),
),
)
this.trackDisposable(
this._neovimInstance.autoCommands.onBufWritePost.subscribe((evt: EventContext) =>
this._onBufWritePost(evt),
),
)
this.trackDisposable(
this._neovimInstance.onColorsChanged.subscribe(() => {
this._onColorsChanged()
}),
)
this.trackDisposable(
this._neovimInstance.onError.subscribe(err => {
this._errorInitializing = true
this._actions.setNeovimError(true)
}),
)
// These functions are mirrors of each other if vim changes dir then oni responds
// and if oni initiates the dir change then we inform vim
// NOTE: the gates to check that the dirs being passed aren't already set prevent
// an infinite loop!!
this.trackDisposable(
this._neovimInstance.onDirectoryChanged.subscribe(async newDirectory => {
if (newDirectory !== this._workspace.activeWorkspace) {
await this._workspace.changeDirectory(newDirectory)
}
}),
)
this.trackDisposable(
this._workspace.onDirectoryChanged.subscribe(async newDirectory => {
if (newDirectory !== this._neovimInstance.currentVimDirectory) {
await this._neovimInstance.chdir(newDirectory)
}
}),
)
// TODO: Add first class event for this
this._neovimInstance.on("action", (action: any) => {
this._renderer.onAction(action)
this._screen.dispatch(action)
this._scheduleRender()
})
this._typingPredictionManager.onPredictionsChanged.subscribe(predictions => {
this._screenWithPredictions.updatePredictions(predictions, this._screen.cursorRow)
this._renderImmediate()
})
this.trackDisposable(
this._neovimInstance.onRedrawComplete.subscribe(() => {
const isCursorInCommandRow = this._screen.cursorRow === this._screen.height - 1
const isCommandLineMode = this.mode && this.mode.indexOf("cmdline") === 0
// In some cases, during redraw, Neovim will actually set the cursor position
// to the command line when rendering. This can happen when 'echo'ing or
// when a popumenu is enabled, and text is writing.
//
// We should ignore those cases, and only set the cursor in the command row
// when we're actually in command line mode. See #1265 for more context.
if (!isCursorInCommandRow || (isCursorInCommandRow && isCommandLineMode)) {
this._actions.setCursorPosition(this._screen)
this._typingPredictionManager.setCursorPosition(this._screen)
}
}),
)
// TODO: Add first class event for this
this._neovimInstance.on("tabline-update", async (currentTabId: number, tabs: ITab[]) => {
const atomicCalls = tabs.map((tab: ITab) => {
return ["nvim_call_function", ["tabpagebuflist", [tab.id]]]
})
const response = await this._neovimInstance.request("nvim_call_atomic", [atomicCalls])
tabs.map((tab: ITab, index: number) => {
tab.buffersInTab = response[0][index] instanceof Array ? response[0][index] : []
})
this._actions.setTabs(currentTabId, tabs)
})
// TODO: Does any disposal need to happen for the observables?
this._cursorMoved$ = asObservable(this._neovimInstance.autoCommands.onCursorMoved).map(
(evt): Oni.Cursor => ({
line: evt.line - 1,
column: evt.column - 1,
}),
)
this._cursorMovedI$ = asObservable(this._neovimInstance.autoCommands.onCursorMovedI).map(
(evt): Oni.Cursor => ({
line: evt.line - 1,
column: evt.column - 1,
}),
)
Observable.merge(this._cursorMoved$, this._cursorMovedI$).subscribe(cursorMoved => {
this.notifyCursorMoved(cursorMoved)
})
this._modeChanged$ = asObservable(this._neovimInstance.onModeChanged)
this._onScroll$ = asObservable(this._neovimInstance.onScroll)
this.trackDisposable(
this._neovimInstance.onModeChanged.subscribe(newMode => this._onModeChanged(newMode)),
)
this.trackDisposable(
this._neovimInstance.onBufferUpdate.subscribe(update => {
const buffer = this._bufferManager.updateBufferFromEvent(update.eventContext)
const bufferUpdate = {
context: update.eventContext,
buffer,
contentChanges: update.contentChanges,
}
this.notifyBufferChanged(bufferUpdate)
this._actions.bufferUpdate(
parseInt(bufferUpdate.buffer.id, 10),
bufferUpdate.buffer.modified,
bufferUpdate.buffer.lineCount,
)
this._syntaxHighlighter.notifyBufferUpdate(bufferUpdate)
}),
)
this.trackDisposable(
this._neovimInstance.onScroll.subscribe((args: EventContext) => {
const convertedArgs: Oni.EditorBufferScrolledEventArgs = {
bufferTotalLines: args.bufferTotalLines,
windowTopLine: args.windowTopLine,
windowBottomLine: args.windowBottomLine,
}
this.notifyBufferScrolled(convertedArgs)
}),
)
addInsertModeLanguageFunctionality(
this._cursorMovedI$,
this._modeChanged$,
this._onScroll$,
this._toolTipsProvider,
)
const textMateHighlightingEnabled = this._configuration.getValue(
"editor.textMateHighlighting.enabled",
)
this._syntaxHighlighter = textMateHighlightingEnabled
? new SyntaxHighlighter(this, this._tokenColors)
: new NullSyntaxHighlighter()
this._completion = new Completion(
this,
this._configuration,
this._completionProviders,
this._languageManager,
this._snippetManager,
this._syntaxHighlighter,
)
this._completionMenu = new CompletionMenu(this._contextMenuManager.create())
this.trackDisposable(
this._completion.onShowCompletionItems.subscribe(completions => {
this._completionMenu.show(completions.filteredCompletions, completions.base)
}),
)
this.trackDisposable(
this._completion.onHideCompletionItems.subscribe(completions => {
this._completionMenu.hide()
}),
)
this.trackDisposable(
this._completionMenu.onItemFocused.subscribe(item => {
this._completion.resolveItem(item)
}),
)
this.trackDisposable(
this._completionMenu.onItemSelected.subscribe(item => {
this._completion.commitItem(item)
}),
)
this._languageIntegration = new LanguageEditorIntegration(
this,
this._configuration,
this._languageManager,
)
this.trackDisposable(
this._languageIntegration.onShowHover.subscribe(async hover => {
const { cursorPixelX, cursorPixelY } = this._store.getState()
await this._hoverRenderer.showQuickInfo(
cursorPixelX,
cursorPixelY,
hover.hover,
hover.errors,
)
}),
)
this.trackDisposable(
this._languageIntegration.onHideHover.subscribe(() => {
this._hoverRenderer.hideQuickInfo()
}),
)
this.trackDisposable(
this._languageIntegration.onShowDefinition.subscribe(definition => {
this._actions.setDefinition(definition.token, definition.location)
}),
)
this.trackDisposable(
this._languageIntegration.onHideDefinition.subscribe(definition => {
this._actions.hideDefinition()
}),
)
this._commands = new NeovimEditorCommands(
commandManager,
this._contextMenuManager,
this._definition,
this._languageIntegration,
this._neovimInstance,
this._rename,
this._symbols,
)
this._renderImmediate()
this._onConfigChanged(this._configuration.getValues())
this.trackDisposable(
this._configuration.onConfigurationChanged.subscribe(
(newValues: Partial) => this._onConfigChanged(newValues),
),
)
// TODO: Factor these out to a place that isn't dependent on a single editor instance
ipcRenderer.on("open-files", (_evt: any, files: string[]) => {
this.openFiles(files)
})
ipcRenderer.on("open-file", (_evt: any, path: string) => {
this._neovimInstance.command(`:e! ${path}`)
})
}
public async blockInput(
inputFunction: (inputCallback: Oni.InputCallbackFunction) => Promise,
): Promise {
return this._neovimInstance.blockInput(inputFunction)
}
public async checkMapping(
key: string,
mode: "n" | "v" | "i",
): Promise<{ key: string; mapping: string }> {
return this._neovimInstance.checkUserMapping({ key, mode })
}
public dispose(): void {
super.dispose()
if (this._neovimInstance) {
this._neovimInstance.dispose()
this._neovimInstance = null
}
if (this._syntaxHighlighter) {
this._syntaxHighlighter.dispose()
this._syntaxHighlighter = null
}
if (this._languageIntegration) {
this._languageIntegration.dispose()
this._languageIntegration = null
}
if (this._completion) {
this._completion.dispose()
this._completion = null
}
if (this._externalMenuOverlay) {
this._externalMenuOverlay.hide()
this._externalMenuOverlay = null
}
if (this._popupMenu) {
this._popupMenu.dispose()
this._popupMenu = null
}
if (this._windowManager) {
this._windowManager.dispose()
this._windowManager = null
}
}
public enter(): void {
Log.info("[NeovimEditor::enter]")
this._onEnterEvent.dispatch()
this._actions.setHasFocus(true)
this._commands.activate()
this._neovimInstance.autoCommands.executeAutoCommand("FocusGained")
this.checkAutoRead()
if (this.activeBuffer) {
this.notifyBufferEnter(this.activeBuffer)
}
}
public checkAutoRead(): void {
// If the user has autoread enabled, we should run ":checktime" on
// focus, as this is needed to get the file to auto-update.
// https://github.com/neovim/neovim/issues/1936
if (
this._neovimInstance.isInitialized &&
this._configuration.getValue("vim.setting.autoread")
) {
this._neovimInstance.command(":checktime")
}
}
public leave(): void {
Log.info("[NeovimEditor::leave]")
this._actions.setHasFocus(false)
this._commands.deactivate()
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")
}
public async setSelection(range: types.Range): Promise {
await this._neovimInstance.input("")
// Clear out any pending block selection
// Without this, if there was a line-wise visual selection,
// range selection would not work correctly.
const atomicCallsVisualMode = [
[
"nvim_call_function",
["setpos", [".", [0, range.start.line + 1, range.start.character + 1]]],
],
["nvim_command", ["normal! v"]],
[
"nvim_call_function",
["setpos", [".", [0, range.end.line + 1, range.end.character + 1]]],
],
]
await this._neovimInstance.request("nvim_call_atomic", [atomicCallsVisualMode])
await this._neovimInstance.input("")
// Re-select the selection and switch to 'select' mode so that typing
// overwrites the selection
const atomicCalls = [
[
"nvim_call_function",
["setpos", ["'<", [0, range.start.line + 1, range.start.character + 1]]],
],
[
"nvim_call_function",
["setpos", ["'>", [0, range.end.line + 1, range.end.character + 1]]],
],
// ["nvim_command", ["normal! v"]],
["nvim_command", ["set selectmode=cmd"]],
["nvim_command", ["normal! gv"]],
["nvim_command", ["set selectmode="]],
]
await this._neovimInstance.request("nvim_call_atomic", [atomicCalls])
}
public async setTextOptions(textOptions: Oni.EditorTextOptions): Promise {
const { insertSpacesForTab, tabSize } = textOptions
if (insertSpacesForTab) {
await this._neovimInstance.command("set expandtab")
} else {
await this._neovimInstance.command("set noexpandtab")
}
await this._neovimInstance.command(
`set tabstop=${tabSize} shiftwidth=${tabSize} softtabstop=${tabSize}`,
)
}
// "v:this_session" |this_session-variable| - is a variable nvim sets to the path of
// the current session file when one is loaded we use it here to check the current session
// if it in oni's session dir then this is updated
public async getCurrentSession(): Promise {
const result = await this._neovimInstance.request("nvim_get_vvar", [
"this_session",
])
if (Array.isArray(result)) {
return this._handleNeovimError(result)
}
return result
}
public async persistSession(session: ISession) {
const result = await this._neovimInstance.command(`mksession! ${session.file}`)
return this._handleNeovimError(result)
}
public async restoreSession(session: ISession) {
await this._neovimInstance.closeAllBuffers()
const result = await this._neovimInstance.command(`source ${session.file}`)
return this._handleNeovimError(result)
}
public async openFile(
file: string,
openOptions: Oni.FileOpenOptions = Oni.DefaultFileOpenOptions,
): Promise {
const tabsMode = this._configuration.getValue("tabs.mode") === "tabs"
const cmd = new Proxy(
{
[Oni.FileOpenMode.NewTab]: "tabnew!",
[Oni.FileOpenMode.HorizontalSplit]: "sp!",
[Oni.FileOpenMode.VerticalSplit]: "vsp!",
[Oni.FileOpenMode.Edit]: tabsMode ? "tab drop" : "e!",
[Oni.FileOpenMode.ExistingTab]: "e!",
},
{
get: (target: { [cmd: string]: string }, name: string) =>
name in target ? target[name] : "e!",
},
)
await this._neovimInstance.command(
`:${cmd[openOptions.openMode]} ${this._escapeSpaces(file)}`,
)
return this.activeBuffer
}
public openFiles = async (
files: string[],
openOptions: Oni.FileOpenOptions = Oni.DefaultFileOpenOptions,
): Promise => {
if (!files) {
return this.activeBuffer
}
// Open the first file in the current buffer if there is no file there,
// otherwise use the passed option.
// Respects the users config and uses "tab drop" for Tab users, and "e!"
// otherwise.
if (this.activeBuffer.filePath === "") {
await this.openFile(files[0], { openMode: Oni.FileOpenMode.Edit })
} else {
await this.openFile(files[0], openOptions)
}
for (let i = 1; i < files.length; i++) {
await this.openFile(files[i], openOptions)
}
return this.activeBuffer
}
public async newFile(filePath: string): Promise {
await this._neovimInstance.command(":vsp " + filePath)
const context = await this._neovimInstance.getContext()
const buffer = this._bufferManager.updateBufferFromEvent(context)
return buffer
}
public executeCommand(command: string): void {
commandManager.executeCommand(command, null)
}
public _onFilesDropped = async (files: FileList) => {
if (files.length) {
const normalisedPaths = Array.from(files).map(f => normalizePath(f.path))
await this.openFiles(normalisedPaths, { openMode: Oni.FileOpenMode.Edit })
}
}
public async init(
filesToOpen: string[],
startOptions?: Partial,
): Promise {
Log.info("[NeovimEditor::init] Called with filesToOpen: " + filesToOpen)
const defaultOptions: INeovimStartOptions = {
runtimePaths: this._pluginManager.getAllRuntimePaths(),
transport: this._configuration.getValue("experimental.neovim.transport"),
neovimPath: this._configuration.getValue("debug.neovimPath"),
loadInitVim: this._configuration.getValue("oni.loadInitVim"),
useDefaultConfig: this._configuration.getValue("oni.useDefaultConfig"),
}
const combinedOptions = {
...defaultOptions,
...startOptions,
}
await this._neovimInstance.start(combinedOptions)
if (this._errorInitializing) {
return
}
VimConfigurationSynchronizer.synchronizeConfiguration(
this._neovimInstance,
this._configuration.getValues(),
)
this._themeManager.onThemeChanged.subscribe(() => {
const newTheme = this._themeManager.activeTheme
if (
newTheme.baseVimTheme &&
(newTheme.baseVimTheme !== this._currentColorScheme ||
newTheme.baseVimBackground !== this._currentBackground)
) {
this.setColorSchemeFromTheme(newTheme)
}
})
if (this._themeManager.activeTheme && this._themeManager.activeTheme.baseVimTheme) {
await this.setColorSchemeFromTheme(this._themeManager.activeTheme)
}
if (filesToOpen && filesToOpen.length > 0) {
await this.openFiles(filesToOpen, { openMode: Oni.FileOpenMode.Edit })
} else {
if (this._configuration.getValue("experimental.welcome.enabled")) {
this._onShowWelcomeScreen.dispatch()
}
}
this._actions.setLoadingComplete()
this._hasLoaded = true
this._isFirstRender = true
this._scheduleRender()
}
public async setColorSchemeFromTheme(theme: IThemeMetadata): Promise {
if (
(theme.baseVimBackground === "dark" || theme.baseVimBackground === "light") &&
theme.baseVimBackground !== this._currentBackground
) {
await this._neovimInstance.command(":set background=" + theme.baseVimBackground)
this._currentBackground = theme.baseVimBackground
}
await this._neovimInstance.command(":color " + theme.baseVimTheme)
}
public getBuffers(): Array {
return this._bufferManager.getBuffers()
}
public async bufferDelete(bufferId: string = this.activeBuffer.id): Promise {
// FIXME: currently this command forces a bufEnter event by navigating away
// from the closed buffer which is currently the only means of updating Oni
// post a BufDelete event
await this._neovimInstance.command(`bd ${bufferId}`)
if (bufferId === "%" || bufferId === this.activeBuffer.id) {
await this._neovimInstance.command(`bnext`)
} else {
await this._neovimInstance.command(`bnext`)
await this._neovimInstance.command(`bprev`)
}
}
public render(): JSX.Element {
const onBufferClose = (bufferId: number) => {
this._neovimInstance.command(`bw! ${bufferId}`)
}
const onBufferSelect = (bufferId: number) => {
this._neovimInstance.command(`buf ${bufferId}`)
}
const onTabClose = (tabId: number) => {
this._neovimInstance.command(`tabclose ${tabId}`)
}
const onTabSelect = (tabId: number) => {
this._neovimInstance.command(`tabn ${tabId}`)
}
const onKeyDown = (key: string) => {
this.input(key)
}
return (
this._onBounceStart()}
onBounceEnd={() => this._onBounceEnd()}
onImeStart={() => this._onImeStart()}
onImeEnd={() => this._onImeEnd()}
onTabClose={onTabClose}
onTabSelect={onTabSelect}
/>
)
}
public async input(key: string): Promise {
if (this._configuration.getValue("debug.fakeLag.neovimInput")) {
await sleep(this._configuration.getValue("debug.fakeLag.neovimInput"))
}
// Check if any of the buffer layers can handle the input...
const buf = this.activeBuffer
const layerInputHandler = buf && buf.handleInput(key)
if (layerInputHandler) {
return
}
await this._neovimInstance.input(key)
}
public async quit(): Promise {
if (this._windowManager) {
this._windowManager.dispose()
this._windowManager = null
}
return this._neovimInstance.quit()
}
private _onBounceStart(): void {
this._actions.setCursorScale(1.1)
}
private _onBounceEnd(): void {
this._actions.setCursorScale(1.0)
}
private _onModeChanged(newMode: string): void {
// 'Bounce' the cursor for show match
if (newMode === "showmatch") {
this._actions.setCursorScale(0.9)
}
this._typingPredictionManager.clearAllPredictions()
if (newMode === "insert" && this._configuration.getValue("editor.typingPrediction")) {
this._typingPredictionManager.enable()
} else {
this._typingPredictionManager.disable()
}
this._actions.setMode(newMode)
this.setMode(newMode as Oni.Vim.Mode)
}
private _updateWindow(currentBuffer: EventContext) {
this._actions.setWindowCursor(
currentBuffer.windowNumber,
currentBuffer.line - 1,
currentBuffer.column - 1,
)
// Convert to 0-based positions
this._syntaxHighlighter.notifyViewportChanged(
currentBuffer.bufferNumber.toString(),
currentBuffer.windowTopLine - 1,
currentBuffer.windowBottomLine - 1,
)
}
private _onFileTypeChanged(evt: EventContext): void {
const buf = this._bufferManager.updateBufferFromEvent(evt)
this._bufferLayerManager.notifyBufferFileTypeChanged(buf)
}
private async _onBufEnter(evt: BufferEventContext): Promise {
const buf = this._bufferManager.updateBufferFromEvent(evt.current)
this._bufferManager.populateBufferList(evt)
this._workspace.autoDetectWorkspace(buf.filePath)
const lastBuffer = this.activeBuffer
if (lastBuffer && lastBuffer.filePath !== buf.filePath) {
this.notifyBufferLeave({
filePath: lastBuffer.filePath,
language: lastBuffer.language,
})
}
this._lastBufferId = evt.current.bufferNumber.toString()
this.notifyBufferEnter(buf)
this._bufferLayerManager.notifyBufferEnter(buf)
// Existing buffers contains a duplicate current buffer object which should be filtered out
// and current buffer sent instead. Finally Filter out falsy viml values.
const existingBuffersWithoutCurrent = evt.existingBuffers.filter(
b => b.bufferNumber !== evt.current.bufferNumber,
)
const buffers = [evt.current, ...existingBuffersWithoutCurrent].filter(b => !!b)
this._actions.bufferEnter(buffers)
}
private _escapeSpaces(str: string): string {
return str.split(" ").join("\\ ")
}
private _onImeStart(): void {
this._actions.setImeActive(true)
}
private _onImeEnd(): void {
this._actions.setImeActive(false)
}
private async _onBufWritePost(evt: EventContext): Promise {
// After we save we aren't modified... but we can pass it in just to be safe
this._actions.bufferSave(evt.bufferNumber, evt.modified, evt.version)
this.notifyBufferSaved({
filePath: evt.bufferFullPath,
language: evt.filetype,
})
}
private async _onBufUnload(evt: BufferEventContext): Promise {
this._bufferManager.populateBufferList(evt)
this._neovimInstance.getBufferIds().then(ids => this._actions.setCurrentBuffers(ids))
}
private async _onBufDelete(evt: BufferEventContext): Promise {
this._bufferManager.populateBufferList(evt)
this._neovimInstance.getBufferIds().then(ids => this._actions.setCurrentBuffers(ids))
}
private async _onBufWipeout(evt: BufferEventContext): Promise {
this._bufferManager.populateBufferList(evt)
this._neovimInstance.getBufferIds().then(ids => this._actions.setCurrentBuffers(ids))
}
private _onConfigChanged(newValues: Partial): void {
const fontFamily = this._configuration.getValue("editor.fontFamily")
const fontSize = addDefaultUnitIfNeeded(this._configuration.getValue("editor.fontSize"))
const fontWeight = this._configuration.getValue("editor.fontWeight")
const linePadding = this._configuration.getValue("editor.linePadding")
this._actions.setFont(fontFamily, fontSize, fontWeight)
this._neovimInstance.setFont(fontFamily, fontSize, fontWeight, linePadding)
Object.keys(newValues).forEach(key => {
const value = newValues[key]
this._actions.setConfigValue(key, value)
})
if (this._hasLoaded) {
VimConfigurationSynchronizer.synchronizeConfiguration(this._neovimInstance, newValues)
}
this._isFirstRender = true
this._scheduleRender()
}
private async _onColorsChanged(): Promise {
const newColorScheme = await this._neovimInstance.eval("g:colors_name")
const { bufferNumber } = await this._neovimInstance.getContext()
this._syntaxHighlighter.notifyColorschemeRedraw(`${bufferNumber}`)
// In error cases, the neovim API layer returns an array
if (typeof newColorScheme !== "string") {
return
}
this._currentColorScheme = newColorScheme
const backgroundColor = this._screen.backgroundColor
const foregroundColor = this._screen.foregroundColor
Log.info(
`[NeovimEditor] Colors changed: ${newColorScheme} - background: ${backgroundColor} foreground: ${foregroundColor}`,
)
this._themeManager.notifyVimThemeChanged(newColorScheme, backgroundColor, foregroundColor)
const tokenColors = await this._neovimInstance.getTokenColors()
this._tokenColors.setDefaultTokenColors(tokenColors)
// Flip first render to force a full redraw
this._isFirstRender = true
this._scheduleRender()
}
private _scheduleRender(): void {
if (this._pendingAnimationFrame) {
return
}
this._pendingAnimationFrame = true
window.requestAnimationFrame(() => this._renderImmediate())
}
private _renderImmediate(): void {
this._pendingAnimationFrame = false
if (this._hasLoaded) {
if (this._isFirstRender) {
this._isFirstRender = false
this._renderer.redrawAll(this._screenWithPredictions as any)
} else {
this._renderer.draw(this._screenWithPredictions as any)
}
}
}
private _handleNeovimError(result: NeovimError | void): void {
if (!result) {
return null
}
// the first value of the error response is a 0
if (Array.isArray(result) && !result[0]) {
const [, error] = result
Log.warn(error)
throw new Error(error)
}
}
}
================================================
FILE: browser/src/Editor/NeovimEditor/NeovimEditorActions.ts
================================================
/**
* ActionCreators.ts
*
* Action Creators are relatively simple - they are just a function that returns an `Action`
*
* For information on Action Creators, check out this link:
* http://redux.js.org/docs/basics/Actions.html
*/
import * as types from "vscode-languageserver-types"
import * as Oni from "oni-api"
import * as State from "./NeovimEditorStore"
import { EventContext, InactiveBufferContext, IScreen } from "./../../neovim"
import { normalizePath } from "./../../Utility"
import { IConfigurationValues } from "./../../Services/Configuration"
import { Errors } from "./../../Services/Diagnostics"
import { IThemeColors } from "./../../Services/Themes"
import { IBufferLayer } from "./../NeovimEditor/BufferLayerManager"
export type DispatchFunction = (action: any) => void
export type GetStateFunction = () => State.IState
export interface ISetHasFocusAction {
type: "SET_HAS_FOCUS"
payload: {
hasFocus: boolean
}
}
export interface ISetLoadingCompleteAction {
type: "SET_LOADING_COMPLETE"
}
export interface ISetColorsAction {
type: "SET_COLORS"
payload: {
colors: IThemeColors
}
}
export interface IAddBufferLayerAction {
type: "ADD_BUFFER_LAYER"
payload: {
bufferId: number
layer: IBufferLayer
}
}
export interface IRemoveBufferLayerAction {
type: "REMOVE_BUFFER_LAYER"
payload: {
bufferId: number
layer: IBufferLayer
}
}
export interface ISetViewportAction {
type: "SET_VIEWPORT"
payload: {
width: number
height: number
}
}
export interface ISetCommandLinePosition {
type: "SET_COMMAND_LINE_POSITION"
payload: {
position: number
level: number
}
}
export interface IHideCommandLineAction {
type: "HIDE_COMMAND_LINE"
}
export interface IShowCommandLineAction {
type: "SHOW_COMMAND_LINE"
payload: {
content: Array<[any, string]>
position: number
firstchar: string
prompt: string
indent: number
level: number
}
}
export interface IWildMenuSelectedAction {
type: "WILDMENU_SELECTED"
payload: {
selected: number
}
}
export interface IShowWildMenuAction {
type: "SHOW_WILDMENU"
payload: {
options: string[]
}
}
export interface IHideWildMenuAction {
type: "HIDE_WILDMENU"
}
export interface ISetNeovimErrorAction {
type: "SET_NEOVIM_ERROR"
payload: {
neovimError: boolean
}
}
export interface ISetCursorScaleAction {
type: "SET_CURSOR_SCALE"
payload: {
cursorScale: number
}
}
export interface ISetCurrentBuffersAction {
type: "SET_CURRENT_BUFFERS"
payload: {
bufferIds: number[]
}
}
export interface ISetImeActive {
type: "SET_IME_ACTIVE"
payload: {
imeActive: boolean
}
}
export interface ISetFont {
type: "SET_FONT"
payload: {
fontFamily: string
fontSize: string
fontWeight: string
}
}
export interface IBufferEnterAction {
type: "BUFFER_ENTER"
payload: {
buffers: State.IBuffer[]
}
}
export interface IShowToolTipAction {
type: "SHOW_TOOL_TIP"
payload: {
id: string
element: JSX.Element
options?: Oni.ToolTip.ToolTipOptions
}
}
export interface IHideToolTipAction {
type: "HIDE_TOOL_TIP"
payload: {
id: string
}
}
export interface IBufferUpdateAction {
type: "BUFFER_UPDATE"
payload: {
id: number
modified: boolean
version: number
totalLines: number
}
}
export interface IBufferSaveAction {
type: "BUFFER_SAVE"
payload: {
id: number
modified: boolean
version: number
}
}
export interface ISetTabs {
type: "SET_TABS"
payload: {
selectedTabId: number
tabs: State.ITab[]
}
}
export interface ISetActiveVimTabPage {
type: "SET_ACTIVE_VIM_TAB_PAGE"
payload: {
id: number
windowIds: number[]
}
}
export interface ISetWindowCursor {
type: "SET_WINDOW_CURSOR"
payload: {
windowId: number
line: number
column: number
}
}
export interface ISetWindowState {
type: "SET_WINDOW_STATE"
payload: {
windowId: number
bufferId: number
file: string
column: number
line: number
dimensions: Oni.Shapes.Rectangle
bufferToScreen: Oni.Coordinates.BufferToScreen
screenToPixel: Oni.Coordinates.ScreenToPixel
bufferToPixel: Oni.Coordinates.BufferToPixel
topBufferLine: number
bottomBufferLine: number
visibleLines: string[]
}
}
export interface ISetInactiveWindowState {
type: "SET_INACTIVE_WINDOW_STATE"
payload: {
windowId: number
dimensions: Oni.Shapes.Rectangle
}
}
export interface ISetErrorsAction {
type: "SET_ERRORS"
payload: {
errors: Errors
}
}
export interface ISetCursorPositionAction {
type: "SET_CURSOR_POSITION"
payload: {
pixelX: number
pixelY: number
fontPixelWidth: number
fontPixelHeight: number
cursorCharacter: string
cursorPixelWidth: number
}
}
export interface ISetModeAction {
type: "SET_MODE"
payload: {
mode: string
}
}
export interface IShowDefinitionAction {
type: "SHOW_DEFINITION"
payload: {
token: Oni.IToken
definitionLocation: types.Location
}
}
export interface IHideDefinitionAction {
type: "HIDE_DEFINITION"
}
export interface ISetConfigurationValue {
type: "SET_CONFIGURATION_VALUE"
payload: {
key: K
value: IConfigurationValues[K]
}
}
export type Action = SimpleAction | ActionWithGeneric
export type SimpleAction =
| IAddBufferLayerAction
| IRemoveBufferLayerAction
| IBufferEnterAction
| IBufferSaveAction
| IBufferUpdateAction
| ISetColorsAction
| ISetCursorPositionAction
| ISetImeActive
| ISetFont
| IHideToolTipAction
| IShowToolTipAction
| IHideDefinitionAction
| IShowDefinitionAction
| ISetModeAction
| ISetCursorScaleAction
| ISetErrorsAction
| ISetCurrentBuffersAction
| ISetHasFocusAction
| ISetNeovimErrorAction
| ISetTabs
| ISetActiveVimTabPage
| ISetLoadingCompleteAction
| ISetViewportAction
| ISetWindowCursor
| ISetWindowState
| ISetInactiveWindowState
| IShowCommandLineAction
| IHideCommandLineAction
| ISetCommandLinePosition
| IHideWildMenuAction
| IShowWildMenuAction
| IWildMenuSelectedAction
export type ActionWithGeneric = ISetConfigurationValue
export const setHasFocus = (hasFocus: boolean) => {
return {
type: "SET_HAS_FOCUS",
payload: {
hasFocus,
},
}
}
export const setLoadingComplete = () => {
return {
type: "SET_LOADING_COMPLETE",
}
}
export const setColors = (colors: IThemeColors) => ({
type: "SET_COLORS",
payload: {
colors,
},
})
export const setCommandLinePosition = ({
pos: position,
level,
}: {
pos: number
level: number
}) => ({
type: "SET_COMMAND_LINE_POSITION",
payload: {
position,
level,
},
})
export const hideCommandLine = () => ({
type: "HIDE_COMMAND_LINE",
})
export const showCommandLine = (
content: Array<[any, string]>,
pos: number,
firstchar: string,
prompt: string,
indent: number,
level: number,
) => ({
type: "SHOW_COMMAND_LINE",
payload: {
content,
position: pos,
firstchar,
prompt,
indent,
level,
},
})
export const showWildMenu = (payload: { options: string[] }) => ({
type: "SHOW_WILDMENU",
payload,
})
export const wildMenuSelect = (payload: { selected: number }) => ({
type: "WILDMENU_SELECTED",
payload,
})
export const hideWildMenu = () => ({
type: "HIDE_WILDMENU",
})
export const setNeovimError = (neovimError: boolean) => ({
type: "SET_NEOVIM_ERROR",
payload: {
neovimError,
},
})
export const setViewport = (width: number, height: number) => ({
type: "SET_VIEWPORT",
payload: {
width,
height,
},
})
export const setCursorScale = (cursorScale: number) => ({
type: "SET_CURSOR_SCALE",
payload: {
cursorScale,
},
})
const formatBuffers = (buffer: InactiveBufferContext & EventContext) => {
return {
id: buffer.bufferNumber,
file: buffer.bufferFullPath ? normalizePath(buffer.bufferFullPath) : "",
totalLines: buffer.bufferTotalLines ? buffer.bufferTotalLines : null,
language: buffer.filetype,
hidden: buffer.hidden,
listed: buffer.listed,
modified: buffer.modified,
}
}
export const addBufferLayer = (
bufferId: number,
layer: Oni.BufferLayer,
): IAddBufferLayerAction => ({
type: "ADD_BUFFER_LAYER",
payload: {
bufferId,
layer,
},
})
export const removeBufferLayer = (
bufferId: number,
layer: Oni.BufferLayer,
): IRemoveBufferLayerAction => ({
type: "REMOVE_BUFFER_LAYER",
payload: {
bufferId,
layer,
},
})
export const bufferEnter = (buffers: Array) => ({
type: "BUFFER_ENTER",
payload: {
buffers: buffers.map(formatBuffers),
},
})
export const bufferUpdate = (id: number, modified: boolean, totalLines: number) => ({
type: "BUFFER_UPDATE",
payload: {
id,
modified,
totalLines,
},
})
export const bufferSave = (id: number, modified: boolean, version: number) => ({
type: "BUFFER_SAVE",
payload: {
id,
modified,
version,
},
})
export const setCurrentBuffers = (bufferIds: number[]) => ({
type: "SET_CURRENT_BUFFERS",
payload: {
bufferIds,
},
})
export const setImeActive = (imeActive: boolean) => ({
type: "SET_IME_ACTIVE",
payload: {
imeActive,
},
})
export const setFont = (fontFamily: string, fontSize: string, fontWeight: string) => ({
type: "SET_FONT",
payload: {
fontFamily,
fontSize,
fontWeight,
},
})
export const setTabs = (selectedTabId: number, tabs: State.ITab[]): ISetTabs => ({
type: "SET_TABS",
payload: {
selectedTabId,
tabs,
},
})
export const setWindowCursor = (windowId: number, line: number, column: number) => ({
type: "SET_WINDOW_CURSOR",
payload: {
windowId,
line,
column,
},
})
export const setWindowState = (
windowId: number,
bufferId: number,
file: string,
column: number,
line: number,
bottomBufferLine: number,
topBufferLine: number,
dimensions: Oni.Shapes.Rectangle,
bufferToScreen: Oni.Coordinates.BufferToScreen,
visibleLines: string[],
) => (dispatch: DispatchFunction, getState: GetStateFunction) => {
const { fontPixelWidth, fontPixelHeight } = getState()
const screenToPixel = (screenSpace: Oni.Coordinates.ScreenSpacePoint) => {
if (
!screenSpace ||
typeof screenSpace.screenX !== "number" ||
typeof screenSpace.screenY !== "number"
) {
return {
pixelX: NaN,
pixelY: NaN,
}
}
return {
pixelX: screenSpace.screenX * fontPixelWidth,
pixelY: screenSpace.screenY * fontPixelHeight,
}
}
const bufferToPixel = (position: types.Position): Oni.Coordinates.PixelSpacePoint => {
const screenPosition = bufferToScreen(position)
if (!screenPosition) {
return null
}
return screenToPixel(screenPosition)
}
dispatch({
type: "SET_WINDOW_STATE",
payload: {
windowId,
bufferId,
file: normalizePath(file),
column,
dimensions,
line,
bufferToScreen,
screenToPixel,
bufferToPixel,
bottomBufferLine,
topBufferLine,
visibleLines,
},
})
}
export const setInactiveWindowState = (
windowId: number,
dimensions: Oni.Shapes.Rectangle,
): ISetInactiveWindowState => ({
type: "SET_INACTIVE_WINDOW_STATE",
payload: {
windowId,
dimensions,
},
})
export const showToolTip = (
id: string,
element: JSX.Element,
options?: Oni.ToolTip.ToolTipOptions,
) => ({
type: "SHOW_TOOL_TIP",
payload: {
id,
element,
options,
},
})
export const hideToolTip = (id: string) => ({
type: "HIDE_TOOL_TIP",
payload: {
id,
},
})
export const setErrors = (errors: Errors) => ({
type: "SET_ERRORS",
payload: {
errors,
},
})
export const setCursorPosition = (screen: IScreen) => (dispatch: DispatchFunction) => {
const cell = screen.getCell(screen.cursorColumn, screen.cursorRow)
dispatch(
_setCursorPosition(
screen.cursorColumn * screen.fontWidthInPixels,
screen.cursorRow * screen.fontHeightInPixels,
screen.fontWidthInPixels,
screen.fontHeightInPixels,
cell.character,
cell.characterWidth * screen.fontWidthInPixels,
),
)
}
export const setMode = (mode: string) => ({
type: "SET_MODE",
payload: { mode },
})
export const setDefinition = (
token: Oni.IToken,
definitionLocation: types.Location,
): IShowDefinitionAction => ({
type: "SHOW_DEFINITION",
payload: {
token,
definitionLocation,
},
})
export const hideDefinition = () => ({
type: "HIDE_DEFINITION",
})
export const setCursorLineOpacity = (opacity: number) => ({
type: "SET_CURSOR_LINE_OPACITY",
payload: {
opacity,
},
})
export const setCursorColumnOpacity = (opacity: number) => ({
type: "SET_CURSOR_COLUMN_OPACITY",
payload: {
opacity,
},
})
export const setActiveVimTabPage = (tabId: number, windowIds: number[]): ISetActiveVimTabPage => ({
type: "SET_ACTIVE_VIM_TAB_PAGE",
payload: {
id: tabId,
windowIds,
},
})
export function setConfigValue(
k: K,
v: IConfigurationValues[K],
): ISetConfigurationValue {
return {
type: "SET_CONFIGURATION_VALUE",
payload: {
key: k,
value: v,
},
}
}
const _setCursorPosition = (
cursorPixelX: any,
cursorPixelY: any,
fontPixelWidth: any,
fontPixelHeight: any,
cursorCharacter: string,
cursorPixelWidth: number,
) => ({
type: "SET_CURSOR_POSITION",
payload: {
pixelX: cursorPixelX,
pixelY: cursorPixelY,
fontPixelWidth,
fontPixelHeight,
cursorCharacter,
cursorPixelWidth,
},
})
================================================
FILE: browser/src/Editor/NeovimEditor/NeovimEditorCommands.ts
================================================
/**
* NeovimEditorCommands
*
* Contextual commands for NeovimEditor
*
*/
import * as os from "os"
import { clipboard } from "electron"
import * as Oni from "oni-api"
import { NeovimInstance } from "./../../neovim"
import { CallbackCommand, CommandManager } from "./../../Services/CommandManager"
import { ContextMenuManager } from "./../../Services/ContextMenu"
import { editorManager } from "./../../Services/EditorManager"
import { findAllReferences, format, LanguageEditorIntegration } from "./../../Services/Language"
import { replaceAll } from "./../../Utility"
import { Definition } from "./Definition"
import { Rename } from "./Rename"
import { Symbols } from "./Symbols"
export class NeovimEditorCommands {
private _lastCommands: CallbackCommand[] = []
constructor(
private _commandManager: CommandManager,
private _contextMenuManager: ContextMenuManager,
private _definition: Definition,
private _languageEditorIntegration: LanguageEditorIntegration,
private _neovimInstance: NeovimInstance,
private _rename: Rename,
private _symbols: Symbols,
) {}
public activate(): void {
const isContextMenuOpen = () => this._contextMenuManager.isMenuOpen()
/**
* Higher-order function for commands dealing with completion
* - checks that the completion menu is open
*/
const contextMenuCommand = (innerCommand: Oni.Commands.CommandCallback) => {
return () => {
if (this._contextMenuManager.isMenuOpen()) {
return innerCommand()
}
return false
}
}
const selectContextMenuItem = contextMenuCommand(() => {
this._contextMenuManager.selectMenuItem()
})
const nextContextMenuItem = contextMenuCommand(() => {
this._contextMenuManager.nextMenuItem()
})
const closeContextMenu = contextMenuCommand(() => {
this._contextMenuManager.closeActiveMenu()
})
const previousContextMenuItem = contextMenuCommand(() => {
this._contextMenuManager.previousMenuItem()
})
const pasteContents = async (neovimInstance: NeovimInstance) => {
const textToPaste = clipboard.readText()
const replacements = { "'": "''" }
replacements[os.EOL] = "\n"
const sanitizedTextLines = replaceAll(textToPaste, replacements)
await neovimInstance.command('let b:oniclipboard=@"')
await neovimInstance.command(`let @"='${sanitizedTextLines}'`)
if (
editorManager.activeEditor.mode === "insert" ||
editorManager.activeEditor.mode === "cmdline_normal"
) {
await neovimInstance.command("set paste")
await neovimInstance.input('"')
await neovimInstance.command("set nopaste")
} else {
await neovimInstance.command("normal! p")
}
await neovimInstance.command('let @"=b:oniclipboard')
await neovimInstance.command("unlet b:oniclipboard")
}
const commands = [
new CallbackCommand(
"contextMenu.select",
null,
null,
selectContextMenuItem,
isContextMenuOpen,
),
new CallbackCommand(
"contextMenu.next",
null,
null,
nextContextMenuItem,
isContextMenuOpen,
),
new CallbackCommand(
"contextMenu.previous",
null,
null,
previousContextMenuItem,
isContextMenuOpen,
),
new CallbackCommand(
"contextMenu.close",
null,
null,
closeContextMenu,
isContextMenuOpen,
),
new CallbackCommand(
"editor.clipboard.paste",
"Clipboard: Paste",
"Paste clipboard contents into active text",
() => pasteContents(this._neovimInstance),
),
new CallbackCommand(
"editor.clipboard.yank",
"Clipboard: Yank",
"Yank contents to clipboard",
() => this._neovimInstance.command('normal! "+y'),
),
new CallbackCommand(
"editor.clipboard.cut",
"Clipboard: Cut",
"Cut contents to clipboard",
() => this._neovimInstance.command('normal! "+x'),
),
new CallbackCommand("oni.editor.findAllReferences", null, null, () =>
findAllReferences(),
),
new CallbackCommand(
"language.findAllReferences",
"Find All References",
"Find all references using a language service",
() => findAllReferences(),
),
new CallbackCommand("language.format", null, null, () => format()),
// TODO: Deprecate
new CallbackCommand("oni.editor.gotoDefinition", null, null, () =>
this._definition.gotoDefinitionUnderCursor(),
),
new CallbackCommand(
"language.gotoDefinition",
"Goto Definition",
"Goto definition using a language service",
() => this._definition.gotoDefinitionUnderCursor(),
),
new CallbackCommand("language.gotoDefinition.openVertical", null, null, () =>
this._definition.gotoDefinitionUnderCursor({
openMode: Oni.FileOpenMode.VerticalSplit,
}),
),
new CallbackCommand("language.gotoDefinition.openHorizontal", null, null, () =>
this._definition.gotoDefinitionUnderCursor({
openMode: Oni.FileOpenMode.HorizontalSplit,
}),
),
new CallbackCommand("language.gotoDefinition.openNewTab", null, null, () =>
this._definition.gotoDefinitionUnderCursor({ openMode: Oni.FileOpenMode.NewTab }),
),
new CallbackCommand("language.gotoDefinition.openEdit", null, null, () =>
this._definition.gotoDefinitionUnderCursor({ openMode: Oni.FileOpenMode.Edit }),
),
new CallbackCommand("language.gotoDefinition.openExistingTab", null, null, () =>
this._definition.gotoDefinitionUnderCursor({
openMode: Oni.FileOpenMode.ExistingTab,
}),
),
new CallbackCommand("editor.rename", "Rename", "Rename an item", () =>
this._rename.startRename(),
),
new CallbackCommand("editor.quickInfo.show", null, null, () =>
this._languageEditorIntegration.showHover(),
),
new CallbackCommand("language.symbols.document", null, null, () =>
this._symbols.openDocumentSymbolsMenu(),
),
new CallbackCommand("language.symbols.workspace", null, null, () =>
this._symbols.openWorkspaceSymbolsMenu(),
),
new CallbackCommand(
"oni.config.openInitVim",
"Configuration: Edit Neovim Config",
"Edit configuration file ('init.vim') for Neovim",
() => this._neovimInstance.openInitVim(),
),
]
this._lastCommands = commands
commands.forEach(c => this._commandManager.registerCommand(c))
}
public deactivate(): void {
this._lastCommands.forEach(c => this._commandManager.unregisterCommand(c.command))
this._lastCommands = []
}
}
================================================
FILE: browser/src/Editor/NeovimEditor/NeovimEditorLoadingOverlay.tsx
================================================
/**
* NeovimEditorLoadingOverlay
*
* Overlay shown over the editor window while initializing (loading Neovim)
*/
import * as React from "react"
import { connect } from "react-redux"
import * as State from "./NeovimEditorStore"
import styled from "styled-components"
import { withProps } from "./../../UI/components/common"
const LoadingSpinnerWrapper = withProps<{}>(styled.div)`
background-color: ${props => props.theme["editor.background"]};
color: ${props => props.theme["editor.foreground"]};
display: flex;
justify-content: center;
align-items: center;
opacity: 1;
transition: opacity 0.15s ease-in;
&.loaded {
opacity: 0;
pointer-events: none;
}
`
export const NeovimEditorLoadingOverlayView = (props: { visible: boolean }): JSX.Element => {
const className = props.visible ? "stack layer" : " stack layer loaded"
return
}
export const mapStateToProps = (state: State.IState): { visible: boolean } => {
return { visible: !state.isLoaded }
}
export const NeovimEditorLoadingOverlay = connect(mapStateToProps)(NeovimEditorLoadingOverlayView)
================================================
FILE: browser/src/Editor/NeovimEditor/NeovimEditorReducer.ts
================================================
/**
* Reducer.ts
*
* Top-level reducer for UI state transforms
*/
import * as State from "./NeovimEditorStore"
import * as Actions from "./NeovimEditorActions"
import { IConfigurationValues } from "./../../Services/Configuration"
import { Errors } from "./../../Services/Diagnostics"
import * as pick from "lodash/pick"
export function reducer(
s: State.IState,
a: Actions.Action,
): State.IState {
if (!s) {
return s
}
switch (a.type) {
case "SET_HAS_FOCUS":
return {
...s,
hasFocus: a.payload.hasFocus,
}
case "SET_COLORS":
return {
...s,
colors: a.payload.colors,
}
case "SET_LOADING_COMPLETE":
return {
...s,
isLoaded: true,
}
case "SET_NEOVIM_ERROR":
return {
...s,
neovimError: a.payload.neovimError,
}
case "SET_VIEWPORT":
return {
...s,
viewport: viewportReducer(s.viewport, a),
}
case "SET_CURSOR_SCALE":
return {
...s,
cursorScale: a.payload.cursorScale,
}
case "SET_ACTIVE_VIM_TAB_PAGE":
return {
...s,
activeVimTabPage: a.payload,
}
case "SET_CURSOR_POSITION":
return {
...s,
cursorPixelX: a.payload.pixelX,
cursorPixelY: a.payload.pixelY,
fontPixelWidth: a.payload.fontPixelWidth,
fontPixelHeight: a.payload.fontPixelHeight,
cursorCharacter: a.payload.cursorCharacter,
cursorPixelWidth: a.payload.cursorPixelWidth,
}
case "SET_IME_ACTIVE":
return {
...s,
imeActive: a.payload.imeActive,
}
case "SET_FONT":
return {
...s,
fontFamily: a.payload.fontFamily,
fontSize: a.payload.fontSize,
fontWeight: a.payload.fontWeight,
}
case "SET_MODE":
return { ...s, ...{ mode: a.payload.mode } }
case "SET_CONFIGURATION_VALUE":
const obj: Partial = {}
obj[a.payload.key] = a.payload.value
const newConfig = { ...s.configuration, ...obj }
return {
...s,
configuration: newConfig,
}
case "SHOW_WILDMENU":
return {
...s,
wildmenu: {
...s.wildmenu,
visible: true,
options: a.payload.options,
},
}
case "WILDMENU_SELECTED":
return {
...s,
wildmenu: {
...s.wildmenu,
selected: a.payload.selected,
},
}
case "HIDE_WILDMENU":
return {
...s,
wildmenu: {
...s.wildmenu,
visible: false,
},
}
case "SHOW_COMMAND_LINE":
// Array<[any, string]>
const [[, content]] = a.payload.content
return {
...s,
commandLine: {
content,
visible: true,
position: a.payload.position,
firstchar: a.payload.firstchar,
prompt: a.payload.prompt,
indent: a.payload.indent,
level: a.payload.level,
},
}
case "HIDE_COMMAND_LINE":
return {
...s,
commandLine: {
visible: false,
content: null,
firstchar: "",
position: null,
prompt: "",
indent: null,
level: null,
},
}
case "SET_COMMAND_LINE_POSITION":
return {
...s,
commandLine: {
...s.commandLine,
position: a.payload.position,
level: a.payload.level,
},
}
default:
return {
...s,
buffers: buffersReducer(s.buffers, a),
definition: definitionReducer(s.definition, a),
layers: layersReducer(s.layers, a),
tabState: tabStateReducer(s.tabState, a),
errors: errorsReducer(s.errors, a),
toolTips: toolTipsReducer(s.toolTips, a),
windowState: windowStateReducer(s.windowState, a),
}
}
}
export const layersReducer = (s: State.Layers, a: Actions.SimpleAction) => {
switch (a.type) {
case "ADD_BUFFER_LAYER": {
const currentLayers = s[a.payload.bufferId] || []
if (currentLayers.find(layer => layer.id === a.payload.layer.id)) {
return s
}
return {
...s,
[a.payload.bufferId]: [...currentLayers, a.payload.layer],
}
}
case "REMOVE_BUFFER_LAYER": {
const currentLayers = s[a.payload.bufferId] || []
return {
...s,
[a.payload.bufferId]: currentLayers.filter(l => l !== a.payload.layer),
}
}
default:
return s
}
}
export const definitionReducer = (s: State.IDefinition, a: Actions.SimpleAction) => {
switch (a.type) {
case "SHOW_DEFINITION":
const { definitionLocation, token } = a.payload
return {
definitionLocation,
token,
}
case "HIDE_DEFINITION":
return null
default:
return s
}
}
export const viewportReducer = (s: State.IViewport, a: Actions.ISetViewportAction) => {
const { width, height } = a.payload
switch (a.type) {
case "SET_VIEWPORT":
return {
...s,
width,
height,
}
default:
return s
}
}
export const tabStateReducer = (s: State.ITabState, a: Actions.SimpleAction): State.ITabState => {
switch (a.type) {
case "SET_TABS":
return {
...s,
...a.payload,
}
default:
return s
}
}
export const buffersReducer = (
s: State.IBufferState,
a: Actions.SimpleAction,
): State.IBufferState => {
let byId = s.byId
let allIds = s.allIds
const emptyBuffer = (id: number): State.IBuffer => ({
id,
file: null,
modified: false,
hidden: true,
listed: false,
totalLines: 0,
})
switch (a.type) {
case "BUFFER_ENTER":
byId = a.payload.buffers.reduce((buffersById, buffer) => {
buffersById[buffer.id] = {
...buffer,
}
return byId
}, byId)
const bufIds = a.payload.buffers.map(b => b.id)
allIds = [...new Set(bufIds)]
return {
activeBufferId: a.payload.buffers[0].id,
byId,
allIds,
}
case "BUFFER_SAVE":
const currentItem = s.byId[a.payload.id] || emptyBuffer(a.payload.id)
byId = {
...s.byId,
[a.payload.id]: {
...currentItem,
id: a.payload.id,
modified: a.payload.modified,
lastSaveVersion: a.payload.version,
},
}
return {
...s,
byId,
}
case "BUFFER_UPDATE":
const currentItem3 = s.byId[a.payload.id] || emptyBuffer(a.payload.id)
// If the last save version hasn't been set, this means it is the first update,
// and should clamp to the incoming version
const lastSaveVersion = currentItem3.lastSaveVersion || a.payload.version
byId = {
...s.byId,
[a.payload.id]: {
...currentItem3,
id: a.payload.id,
modified: a.payload.modified,
totalLines: a.payload.totalLines,
lastSaveVersion,
},
}
return {
...s,
byId,
}
case "SET_CURRENT_BUFFERS":
allIds = s.allIds.filter(id => a.payload.bufferIds.indexOf(id) >= 0)
let activeBufferId = s.activeBufferId
if (a.payload.bufferIds.indexOf(activeBufferId) === -1) {
activeBufferId = null
}
const newById: any = pick(s.byId, a.payload.bufferIds)
return {
activeBufferId,
byId: newById,
allIds,
}
default:
return s
}
}
export const errorsReducer = (s: Errors, a: Actions.SimpleAction) => {
switch (a.type) {
case "SET_ERRORS":
return {
...a.payload.errors,
}
default:
return s
}
}
export const toolTipsReducer = (s: State.ToolTips, a: Actions.SimpleAction): State.ToolTips => {
switch (a.type) {
case "SHOW_TOOL_TIP":
const existingItem = s[a.payload.id] || {}
const newItem = {
...existingItem,
...a.payload,
}
return {
...s,
[a.payload.id]: newItem,
}
case "HIDE_TOOL_TIP":
return {
...s,
[a.payload.id]: null,
}
default:
return s
}
}
export const windowStateReducer = (
s: State.IWindowState,
a: Actions.SimpleAction,
): State.IWindowState => {
let currentWindow
switch (a.type) {
case "SET_WINDOW_CURSOR":
currentWindow = s.windows[a.payload.windowId] || null
return {
activeWindow: a.payload.windowId,
windows: {
...s.windows,
[a.payload.windowId]: {
...currentWindow,
column: a.payload.column,
line: a.payload.line,
},
},
}
case "SET_INACTIVE_WINDOW_STATE":
currentWindow = s.windows[a.payload.windowId] || null
return {
...s,
windows: {
...s.windows,
[a.payload.windowId]: {
...currentWindow,
windowId: a.payload.windowId,
column: -1,
line: -1,
topBufferLine: -1,
bottomBufferLine: -1,
dimensions: a.payload.dimensions,
},
},
}
case "SET_WINDOW_STATE":
currentWindow = s.windows[a.payload.windowId] || null
return {
activeWindow: a.payload.windowId,
windows: {
...s.windows,
[a.payload.windowId]: {
...currentWindow,
file: a.payload.file,
bufferId: a.payload.bufferId,
windowId: a.payload.windowId,
column: a.payload.column,
line: a.payload.line,
bufferToScreen: a.payload.bufferToScreen,
screenToPixel: a.payload.screenToPixel,
bufferToPixel: a.payload.bufferToPixel,
dimensions: a.payload.dimensions,
topBufferLine: a.payload.topBufferLine,
bottomBufferLine: a.payload.bottomBufferLine,
visibleLines: a.payload.visibleLines,
},
},
}
default:
return s
}
}
================================================
FILE: browser/src/Editor/NeovimEditor/NeovimEditorSelectors.ts
================================================
/**
* Selectors.ts
*
* Selectors are basically helper methods for operating on the State
* See Redux documents here fore more info:
* http://redux.js.org/docs/recipes/ComputingDerivedData.html
*/
import * as types from "vscode-languageserver-types"
import * as Oni from "oni-api"
import { createSelector } from "reselect"
import { getAllErrorsForFile } from "./../../Services/Diagnostics"
import * as Utility from "./../../Utility"
import * as State from "./NeovimEditorStore"
export const EmptyArray: any[] = []
const getWindows = (state: State.IState) => state.windowState
export const getActiveWindow = createSelector([getWindows], windowState => {
if (windowState.activeWindow === null) {
return null
}
const activeWindow = windowState.activeWindow
return windowState.windows[activeWindow]
})
const emptyRectangle: Oni.Shapes.Rectangle = {
x: 0,
y: 0,
width: 0,
height: 0,
}
export const getFontPixelWidthHeight = (state: State.IState) => ({
fontPixelWidth: state.fontPixelWidth,
fontPixelHeight: state.fontPixelHeight,
})
export const getActiveWindowScreenDimensions = createSelector([getActiveWindow], win => {
if (!win || !win.dimensions) {
return emptyRectangle
}
return win.dimensions
})
export const getActiveWindowPixelDimensions = createSelector(
[getActiveWindowScreenDimensions, getFontPixelWidthHeight],
(dimensions, fontSize) => {
const pixelDimensions = {
x: dimensions.x * fontSize.fontPixelWidth,
y: dimensions.y * fontSize.fontPixelHeight,
width: dimensions.width * fontSize.fontPixelWidth,
height: dimensions.height * fontSize.fontPixelHeight,
}
return pixelDimensions
},
)
export const getErrors = (state: State.IState) => state.errors
export const getErrorsForActiveFile = createSelector(
[getActiveWindow, getErrors],
(win, errors) => {
const errorsForFile: types.Diagnostic[] =
win && win.file
? getAllErrorsForFile(win.file, errors)
: (EmptyArray as types.Diagnostic[])
return errorsForFile
},
)
export const getErrorsForPosition = createSelector(
[getActiveWindow, getErrorsForActiveFile],
(win, errors) => {
if (!win) {
return EmptyArray
}
const { line, column } = win
return errors.filter(diag => Utility.isInRange(line, column, diag.range))
},
)
const getBufferState = (state: State.IState) => state.buffers
export const getAllBuffers = createSelector([getBufferState], buffers =>
buffers.allIds.map(id => buffers.byId[id]).filter(buf => buf.listed),
)
export const getBufferMetadata = createSelector([getAllBuffers], buffers =>
buffers.map(b => ({
id: b.id,
file: b.file,
modified: b.modified,
})),
)
export const getActiveBuffer = createSelector([getActiveWindow, getAllBuffers], (win, buffers) => {
if (!win || !win.file) {
return null
}
const buf = buffers.find(b => b.file === win.file)
return buf || null
})
export const getActiveBufferId = createSelector(
[getActiveBuffer],
buf => (buf === null ? null : buf.id),
)
================================================
FILE: browser/src/Editor/NeovimEditor/NeovimEditorStore.ts
================================================
/**
* State.ts
*
* This file describes the Redux state of the app
*/
import * as types from "vscode-languageserver-types"
import * as Oni from "oni-api"
import { Store } from "redux"
import thunk from "redux-thunk"
import { IConfigurationValues } from "./../../Services/Configuration"
import { DefaultThemeColors, IThemeColors } from "./../../Services/Themes"
import { createStore as createReduxStore } from "./../../Redux"
import { IBufferLayer } from "./../NeovimEditor/BufferLayerManager"
export interface Layers {
[id: number]: IBufferLayer[]
}
export interface Buffers {
[filePath: string]: IBuffer
}
export interface Errors {
[file: string]: { [key: string]: types.Diagnostic[] }
}
export interface ToolTips {
[id: string]: IToolTip
}
import { reducer } from "./NeovimEditorReducer"
/**
* Viewport encompasses the actual 'app' height
*/
export interface IViewport {
width: number
height: number
}
export interface IToolTip {
id: string
options?: Oni.ToolTip.ToolTipOptions
element: JSX.Element
}
export interface IState {
// Editor
cursorScale: number
cursorPixelX: number
cursorPixelY: number
cursorPixelWidth: number
cursorCharacter: string
fontPixelWidth: number
fontPixelHeight: number
fontFamily: string
fontSize: string
fontWeight: string
hasFocus: boolean
mode: string
definition: null | IDefinition
cursorLineOpacity: number
cursorColumnOpacity: number
configuration: IConfigurationValues
imeActive: boolean
isLoaded: boolean
viewport: IViewport
colors: IThemeColors
toolTips: ToolTips
neovimError: boolean
/**
* Tabs refer to the Vim-concept of tabs
*/
tabState: ITabState
buffers: IBufferState
layers: Layers
windowState: IWindowState
errors: Errors
activeVimTabPage: IVimTabPage
commandLine: ICommandLine | null
wildmenu: IWildMenu
}
export interface IWildMenu {
selected: number
visible: boolean
options: string[]
}
export interface ICommandLine {
visible: boolean
content: string
firstchar: string
position: number
prompt: string
indent: number
level: number
}
export interface IDefinition {
token: Oni.IToken
definitionLocation: types.Location
}
export interface IBufferState {
activeBufferId: number
byId: { [id: number]: IBuffer }
allIds: number[]
}
export interface IBuffer {
id: number
file: string
modified: boolean
lastSaveVersion?: number
version?: number
totalLines: number
hidden: boolean
listed: boolean
}
export interface ITab {
id: number
name: string
buffersInTab: number[]
}
export interface ITabState {
selectedTabId: number | null
tabs: ITab[]
}
export interface IVimTabPage {
id: number
windowIds: number[]
}
export interface IWindowState {
activeWindow: number
windows: { [windowId: number]: IWindow }
}
export interface IWindow {
file: string
bufferId: number
windowId: number
column: number
line: number
bufferToScreen: Oni.Coordinates.BufferToScreen
screenToPixel: Oni.Coordinates.ScreenToPixel
bufferToPixel: Oni.Coordinates.BufferToPixel
dimensions: Oni.Shapes.Rectangle
topBufferLine: number
bottomBufferLine: number
visibleLines: string[]
}
export function readConf(
conf: IConfigurationValues,
k: K,
): IConfigurationValues[K] {
if (!conf) {
return null
} else {
return conf[k]
}
}
export const createDefaultState = (): IState => ({
cursorScale: 1,
cursorPixelX: 10,
cursorPixelY: 10,
cursorPixelWidth: 10,
cursorCharacter: "",
fontPixelWidth: 10,
fontPixelHeight: 10,
fontFamily: "",
fontSize: "",
fontWeight: "",
hasFocus: false,
imeActive: false,
mode: "normal",
definition: null,
colors: DefaultThemeColors,
cursorLineOpacity: 0,
cursorColumnOpacity: 0,
neovimError: false,
isLoaded: false,
activeVimTabPage: null,
configuration: {} as IConfigurationValues,
buffers: {
activeBufferId: null,
byId: {},
allIds: [],
},
layers: {},
tabState: {
selectedTabId: null,
tabs: [],
},
windowState: {
activeWindow: null,
windows: {},
},
viewport: {
width: 0,
height: 0,
},
errors: {},
toolTips: {},
commandLine: {
content: null,
prompt: null,
indent: null,
level: null,
visible: false,
firstchar: "",
position: 0,
},
wildmenu: {
selected: null,
visible: false,
options: [],
},
})
let neovimEditorId = 0
export const createStore = (): Store => {
const editorId = neovimEditorId++
return createReduxStore("NeovimEditor" + editorId.toString(), reducer, createDefaultState(), [
thunk,
])
}
================================================
FILE: browser/src/Editor/NeovimEditor/NeovimInput.tsx
================================================
/**
* NeovimInput.tsx
*
* Layer responsible for handling Neovim input interactiosn
*/
import * as React from "react"
import { IEvent } from "oni-types"
import { Mouse } from "./../../Input/Mouse"
import { NeovimInstance, NeovimScreen } from "./../../neovim"
import { TypingPredictionManager } from "./../../Services/TypingPredictionManager"
import { KeyboardInput } from "./../../Input/KeyboardInput"
export interface INeovimInputProps {
neovimInstance: NeovimInstance
screen: NeovimScreen
onActivate: IEvent
onBounceStart: () => void
onBounceEnd: () => void
onImeStart: () => void
onImeEnd: () => void
onKeyDown?: (key: string) => void
startActive?: boolean
typingPrediction: TypingPredictionManager
}
export class NeovimInput extends React.PureComponent {
private _mouseElement: HTMLDivElement
private _mouse: Mouse
public componentDidMount(): void {
if (this._mouseElement) {
this._mouse = new Mouse(this._mouseElement, this.props.screen)
this._mouse.on("mouse", (mouseInput: string) => {
this.props.neovimInstance.input(mouseInput)
})
}
}
public render(): JSX.Element {
return (
(this._mouseElement = elem)} className="stack enable-mouse">
)
}
}
================================================
FILE: browser/src/Editor/NeovimEditor/NeovimPopupMenu.tsx
================================================
/**
* NeovimPopupMenu.tsx
*
* Implementation of Neovim's popup menu
*/
import * as React from "react"
import * as Oni from "oni-api"
import { IEvent } from "oni-types"
import { INeovimCompletionInfo, INeovimCompletionItem } from "./../../neovim"
import { ContextMenuView, IContextMenuItem } from "./../../Services/ContextMenu"
import { IToolTipsProvider } from "./ToolTipsProvider"
const mapNeovimCompletionItemToContextMenuItem = (
item: INeovimCompletionItem,
idx: number,
totalLength: number,
): IContextMenuItem => ({
label: item.word,
detail: item.menu,
documentation: (idx + 1).toString() + " of " + totalLength.toString(),
icon: "align-right",
})
export class NeovimPopupMenu {
private _lastItems: IContextMenuItem[] = []
constructor(
private _popupMenuShowEvent: IEvent,
private _popupMenuHideEvent: IEvent,
private _popupMenuSelectEvent: IEvent,
private _onBufferEnterEvent: IEvent,
private _toolTipsProvider: IToolTipsProvider,
) {
this._popupMenuShowEvent.subscribe(completionInfo => {
this._lastItems = completionInfo.items.map((i, idx) =>
mapNeovimCompletionItemToContextMenuItem(i, idx, completionInfo.items.length),
)
this._renderCompletionMenu(completionInfo.selectedIndex)
})
this._popupMenuSelectEvent.subscribe(idx => {
this._renderCompletionMenu(idx)
})
this._popupMenuHideEvent.subscribe(() => {
this._toolTipsProvider.hideToolTip("nvim-popup")
})
this._onBufferEnterEvent.subscribe(() => {
this._toolTipsProvider.hideToolTip("nvim-popup")
})
}
public dispose(): void {
// TODO: Implement 'unsubscribe' logic here
// tslint:disable-line
}
private _renderCompletionMenu(selectedIndex: number): void {
const itemsToRender: IContextMenuItem[] = this._lastItems
const completionElement = (
)
this._toolTipsProvider.showToolTip("nvim-popup", completionElement, {
position: null,
openDirection: 2,
padding: "0px",
})
}
}
================================================
FILE: browser/src/Editor/NeovimEditor/NeovimRenderer.tsx
================================================
/**
* NeovimRenderer.tsx
*
* Layer responsible for invoking the INeovimRender strategy and applying to the DOM
*/
import * as React from "react"
import { IScreen, NeovimInstance } from "./../../neovim"
import { INeovimRenderer } from "./../../Renderer"
export interface INeovimRendererProps {
neovimInstance: NeovimInstance
screen: IScreen
renderer: INeovimRenderer
}
export class NeovimRenderer extends React.PureComponent {
private _element: HTMLDivElement
private _boundOnResizeMethod: any
private _resizeObserver: any
public componentDidMount(): void {
if (this._element) {
this.props.renderer.start(this._element)
this._onResize()
}
if (!this._boundOnResizeMethod) {
this._boundOnResizeMethod = this._onResize.bind(this)
// tslint:disable-next-line no-string-literal
this._resizeObserver = new window["ResizeObserver"]((entries: any) => {
if (this._boundOnResizeMethod) {
this._boundOnResizeMethod()
}
})
this._resizeObserver.observe(this._element)
}
}
public componentWillUnmount(): void {
// TODO: Stop renderer
if (this._resizeObserver) {
this._resizeObserver.disconnect()
this._resizeObserver = null
}
if (this._boundOnResizeMethod) {
this._boundOnResizeMethod = null
}
}
public render(): JSX.Element {
return (this._element = elem)} className="stack layer" />
}
private _onResize(): void {
if (!this._element) {
return
}
const width = this._element.offsetWidth
const height = this._element.offsetHeight
this.props.neovimInstance
.resize(width, height)
.then(() => this.props.renderer.redrawAll(this.props.screen))
}
}
================================================
FILE: browser/src/Editor/NeovimEditor/NeovimSurface.tsx
================================================
/**
* NeovimSurface.tsx
*
* UI layer for the Neovim editor surface
*/
import * as React from "react"
import { connect } from "react-redux"
import { IEvent } from "oni-types"
import { NeovimInstance, NeovimScreen } from "./../../neovim"
import { INeovimRenderer } from "./../../Renderer"
import FileDropHandler from "./FileDropHandler"
import { TypingPredictionManager } from "./../../Services/TypingPredictionManager"
import { Cursor } from "./../../UI/components/Cursor"
import { CursorLine } from "./../../UI/components/CursorLine"
import { InstallHelp } from "./../../UI/components/InstallHelp"
import { TabsContainer } from "./../../UI/components/Tabs"
import { ToolTips } from "./../../UI/components/ToolTip"
import { StackLayer } from "../../UI/components/common"
import { setViewport } from "./../NeovimEditor/NeovimEditorActions"
import { NeovimBufferLayers } from "./NeovimBufferLayersView"
import { NeovimEditorLoadingOverlay } from "./NeovimEditorLoadingOverlay"
import { NeovimInput } from "./NeovimInput"
import { NeovimRenderer } from "./NeovimRenderer"
export interface INeovimSurfaceProps {
autoFocus: boolean
neovimInstance: NeovimInstance
renderer: INeovimRenderer
screen: NeovimScreen
typingPrediction: TypingPredictionManager
onActivate: IEvent
onKeyDown?: (key: string) => void
onBufferClose?: (bufferId: number) => void
onBufferSelect?: (bufferId: number) => void
onFileDrop?: (files: FileList) => void
onImeStart: () => void
onImeEnd: () => void
onBounceStart: () => void
onBounceEnd: () => void
onTabClose?: (tabId: number) => void
onTabSelect?: (tabId: number) => void
setViewport: any
}
class NeovimSurface extends React.Component {
private observer: any
private _editor: HTMLDivElement
public componentDidMount(): void {
// tslint:disable-next-line
this.observer = new window["ResizeObserver"](([entry]: any) => {
this.setDimensions(entry.contentRect.width, entry.contentRect.height)
})
this.observer.observe(this._editor)
}
public setDimensions = (width: number, height: number) => {
this.props.setViewport(width, height)
}
public render(): JSX.Element {
return (
{({ setRef }) => (
)}
)
}
}
export default connect(null, { setViewport })(NeovimSurface)
================================================
FILE: browser/src/Editor/NeovimEditor/Rename.tsx
================================================
/**
* Rename.tsx
*/
import * as React from "react"
import * as Oni from "oni-api"
import * as Log from "oni-core-logging"
import * as Helpers from "./../../Plugins/Api/LanguageClient/LanguageClientHelpers"
import { LanguageManager } from "./../../Services/Language"
import { RenameView } from "./../../Services/Language/RenameView"
import { Workspace } from "./../../Services/Workspace"
import { IToolTipsProvider } from "./ToolTipsProvider"
const _renameToolTipName = "rename-tool-tip"
export class Rename {
private _isRenameActive: boolean
constructor(
private _editor: Oni.Editor,
private _languageManager: LanguageManager,
private _toolTipsProvider: IToolTipsProvider,
private _workspace: Workspace,
) {}
public async startRename(): Promise {
if (this._isRenameActive) {
return
}
const activeBuffer = this._editor.activeBuffer
const activeToken = await activeBuffer.getTokenAt(
activeBuffer.cursor.line,
activeBuffer.cursor.column,
)
if (!activeToken || !activeToken.tokenName) {
return
}
this._isRenameActive = true
this._toolTipsProvider.showToolTip(
_renameToolTipName,
this.cancelRename()}
onComplete={newValue => this.commitRename(newValue)}
tokenName={activeToken.tokenName}
/>,
{
position: null,
openDirection: 2,
onDismiss: () => this.cancelRename(),
},
)
}
public commitRename(newValue: string): void {
Log.verbose("[RENAME] Committing rename")
this.doRename(newValue)
this.closeToolTip()
}
public cancelRename(): void {
Log.verbose("[RENAME] Cancelling")
this.closeToolTip()
}
public closeToolTip(): void {
Log.verbose("[RENAME] closeToolTip")
this._isRenameActive = false
this._toolTipsProvider.hideToolTip(_renameToolTipName)
}
public async doRename(newName: string): Promise {
const activeBuffer = this._editor.activeBuffer
const args = {
textDocument: {
uri: Helpers.wrapPathInFileUri(activeBuffer.filePath),
},
position: {
line: activeBuffer.cursor.line,
character: activeBuffer.cursor.column,
},
newName,
}
let result = null
try {
result = await this._languageManager.sendLanguageServerRequest(
activeBuffer.language,
activeBuffer.filePath,
"textDocument/rename",
args,
)
} catch (ex) {
Log.debug(ex)
}
if (result) {
await this._workspace.applyEdits(result)
}
}
}
================================================
FILE: browser/src/Editor/NeovimEditor/Symbols.ts
================================================
/**
* CodeAction.ts
*
*/
import * as _ from "lodash"
import { ErrorCodes } from "vscode-jsonrpc/lib/messages"
import * as types from "vscode-languageserver-types"
import * as Oni from "oni-api"
import * as Log from "oni-core-logging"
import { LanguageManager } from "./../../Services/Language"
import { Menu, MenuManager } from "./../../Services/Menu"
import * as Helpers from "./../../Plugins/Api/LanguageClient/LanguageClientHelpers"
import { asObservable, sleep } from "./../../Utility"
import { Definition } from "./Definition"
export class Symbols {
constructor(
private _editor: Oni.Editor,
private _definition: Definition,
private _languageManager: LanguageManager,
private _menuManager: MenuManager,
) {}
public async openWorkspaceSymbolsMenu() {
const menu = this._menuManager.create()
menu.show()
menu.setItems([
{
label: "Type to search symbols....",
},
])
menu.setLoading(true)
const filterTextChanged$ = asObservable(menu.onFilterTextChanged)
menu.onItemSelected.subscribe((selectedItem: Oni.Menu.MenuOption) => {
const key = selectedItem.label + selectedItem.detail
const loc = keyToLocation[key]
if (loc) {
this._definition.gotoPositionInUri(
loc.uri,
loc.range.start.line,
loc.range.start.character,
)
}
})
let keyToLocation: any = {}
const getKey = (si: types.SymbolInformation) => si.name + this._getDetailFromSymbol(si)
filterTextChanged$
.debounceTime(25)
.do(() => menu.setLoading(true))
.concatMap(async (newText: string) => {
return this._requestSymbols(this._editor.activeBuffer, "workspace/symbol", menu, {
query: newText,
})
})
.subscribe((newItems: types.SymbolInformation[]) => {
menu.setLoading(false)
menu.setItems(newItems.map(item => this._symbolInfoToMenuItem(item)))
keyToLocation = newItems.reduce((prev, curr) => {
return {
...prev,
[getKey(curr)]: curr.location,
}
}, {})
})
}
public async openDocumentSymbolsMenu(): Promise {
const menu = this._menuManager.create()
menu.show()
menu.setLoading(true)
const buffer = this._editor.activeBuffer
const result: types.SymbolInformation[] = await this._requestSymbols(
buffer,
"textDocument/documentSymbol",
menu,
)
const options: Oni.Menu.MenuOption[] = result.map(item => this._symbolInfoToMenuItem(item))
const labelToLocation = result.reduce((prev, curr) => {
return {
...prev,
[curr.name]: curr.location,
}
}, {})
menu.onItemSelected.subscribe(selectedItem => {
const location: types.Location = labelToLocation[selectedItem.label]
if (location) {
this._definition.gotoPositionInUri(
location.uri,
location.range.start.line,
location.range.start.character,
)
}
})
menu.setItems(options)
menu.setLoading(false)
}
private _getDetailFromSymbol(si: types.SymbolInformation): string {
const unwrappedPath = Helpers.unwrapFileUriPath(si.location.uri)
if (si.containerName) {
return si.containerName + "|" + unwrappedPath
} else {
return unwrappedPath
}
}
private _symbolInfoToMenuItem(si: types.SymbolInformation): Oni.Menu.MenuOption {
return {
label: si.name,
detail: this._getDetailFromSymbol(si),
icon: this._convertSymbolKindToIconName(si.kind),
}
}
private _convertSymbolKindToIconName(symbolKind: types.SymbolKind): string {
switch (symbolKind) {
case types.SymbolKind.Class:
return "cube"
case types.SymbolKind.Constructor:
return "building"
case types.SymbolKind.Enum:
return "sitemap"
case types.SymbolKind.Field:
return "var"
case types.SymbolKind.File:
return "file"
case types.SymbolKind.Function:
return "cog"
case types.SymbolKind.Interface:
return "plug"
case types.SymbolKind.Method:
return "flash"
case types.SymbolKind.Module:
return "cubes"
case types.SymbolKind.Property:
return "wrench"
case types.SymbolKind.Variable:
return "code"
default:
return "question"
}
}
/**
* Send a request for symbols, retrying if the server is not ready, as long as the menu is open.
*/
private async _requestSymbols(
buffer: Oni.Buffer,
command: string,
menu: Menu,
options: any = {},
): Promise {
while (menu.isOpen()) {
try {
return await this._languageManager.sendLanguageServerRequest(
buffer.language,
buffer.filePath,
command,
_.extend(
{
textDocument: {
uri: Helpers.wrapPathInFileUri(buffer.filePath),
},
},
options,
),
)
} catch (e) {
if (e.code === ErrorCodes.ServerNotInitialized) {
Log.warn("[Symbols] Language server not yet initialised, trying again...")
await sleep(1000)
} else {
throw e
}
}
}
return []
}
}
================================================
FILE: browser/src/Editor/NeovimEditor/ToolTipsProvider.ts
================================================
import * as Oni from "oni-api"
import * as Actions from "./NeovimEditorActions"
export interface IToolTipsProvider {
showToolTip(id: string, element: JSX.Element, options: Oni.ToolTip.ToolTipOptions): void
hideToolTip(id: string): void
}
export class NeovimEditorToolTipsProvider implements IToolTipsProvider {
constructor(private _actions: typeof Actions) {}
public showToolTip(
id: string,
element: JSX.Element,
options: Oni.ToolTip.ToolTipOptions,
): void {
this._actions.showToolTip(id, element, options)
}
public hideToolTip(id: string): void {
this._actions.hideToolTip(id)
}
}
================================================
FILE: browser/src/Editor/NeovimEditor/WelcomeBufferLayer.tsx
================================================
/**
* NeovimEditor.ts
*
* IEditor implementation for Neovim
*/
import * as Oni from "oni-api"
import * as Log from "oni-core-logging"
import { Event } from "oni-types"
import * as React from "react"
import { getMetadata } from "./../../Services/Metadata"
import { ISession, SessionManager } from "./../../Services/Sessions"
import styled, {
boxShadowInset,
Css,
css,
enableMouse,
getSelectedBorder,
keyframes,
lighten,
} from "./../../UI/components/common"
import { Icon } from "./../../UI/Icon"
// const entrance = keyframes`
// 0% { opacity: 0; transform: translateY(2px); }
// 100% { opacity: 0.5; transform: translateY(0px); }
// `
// const enterLeft = keyframes`
// 0% { opacity: 0; transform: translateX(-4px); }
// 100% { opacity: 1; transform: translateX(0px); }
// `
// const enterRight = keyframes`
// 0% { opacity: 0; transform: translateX(4px); }
// 100% { opacity: 1; transform: translateX(0px); }
// `
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
justify?: string
flex?: string
height?: string
extension?: Css
}
const Column = styled("div")`
background: inherit;
display: flex;
justify-content: ${({ justify }) => justify || `center`};
align-items: ${({ alignment }) => alignment || "center"};
flex-direction: column;
width: 100%;
flex: ${({ flex }) => flex || "1"};
height: ${({ height }) => height || `auto`};
${({ extension }) => extension};
`
const sectionStyles = css`
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
height: 90%;
overflow-y: hidden;
direction: rtl;
&:hover {
overflow-y: overlay;
}
& > * {
direction: ltr;
}
`
const LeftColumn = styled.div`
${sectionStyles};
padding: 0;
padding-left: 1rem;
overflow-y: hidden;
width: 60%;
`
const RightColumn = styled.div`
${sectionStyles};
width: 30%;
border-left: 1px solid ${({ theme }) => theme["editor.background"]};
`
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`
font-size: 2em;
text-align: right;
`
const SubtitleText = styled.div`
font-size: 1.2em;
text-align: right;
`
const HeroImage = styled.img`
width: 192px;
height: 192px;
opacity: 0.4;
`
export const SectionHeader = styled.div`
margin-top: 1em;
margin-bottom: 1em;
font-size: 1.2em;
font-weight: bold;
text-align: left;
width: 100%;
`
const WelcomeButtonHoverStyled = `
transform: translateY(-1px);
box-shadow: 0 4px 8px 2px rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
`
export interface WelcomeButtonWrapperProps {
isSelected: boolean
borderSize: string
}
const WelcomeButtonWrapper = styled("button")`
box-sizing: border-box;
font-size: inherit;
font-family: inherit;
border: 0px solid ${props => props.theme.foreground};
border-left: ${getSelectedBorder};
border-right: 4px solid transparent;
cursor: pointer;
color: ${({ theme }) => theme.foreground};
background-color: ${({ theme }) => lighten(theme.background)};
transform: ${({ isSelected }) => (isSelected ? "translateX(-4px)" : "translateX(0px)")};
transition: transform 0.25s;
width: 100%;
margin: 0.8em 0;
padding: 0.8em;
display: flex;
flex-direction: row;
&:hover {
${WelcomeButtonHoverStyled};
}
`
const AnimatedContainer = styled<{ duration: string }, "div">("div")`
width: 100%;
animation: ${entranceFull} ${p => p.duration} ease-in 1s both;
`
const WelcomeButtonTitle = styled.span`
font-size: 1em;
font-weight: bold;
margin: 0.4em;
width: 100%;
text-align: left;
`
const WelcomeButtonDescription = styled.span`
font-size: 0.8em;
opacity: 0.75;
margin: 4px;
width: 100%;
text-align: right;
`
const boxStyling = css`
width: 60%;
height: 60%;
padding: 0 1em;
opacity: 1;
margin-top: 64px;
box-sizing: border-box;
border: 1px solid ${p => p.theme["editor.hover.contents.background"]};
border-radius: 4px;
overflow: hidden;
justify-content: space-around;
background-color: ${p => p.theme["editor.hover.contents.codeblock.background"]};
${boxShadowInset};
`
const titleRow = css`
width: 100%;
padding-top: 32px;
animation: ${entranceFull} 0.25s ease-in 0.25s forwards};
`
const selectedSectionItem = css`
${({ theme }) => `
text-decoration: underline;
color: ${theme["highlight.mode.normal.background"]};
`};
`
export const SectionItem = styled<{ isSelected?: boolean }, "li">("li")`
width: 100%;
margin: 0.2em;
text-align: left;
height: auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
${({ isSelected }) => isSelected && selectedSectionItem};
&:hover {
text-decoration: underline;
}
`
export const SessionsList = styled.ul`
width: 70%;
margin: 0;
list-style-type: none;
border-radius: 4px;
padding: 0 1em;
border: 1px solid ${p => p.theme["editor.hover.contents.codeblock.background"]};
`
export interface WelcomeButtonProps {
title: string
description: string
command: string
selected: boolean
onClick: () => void
}
interface IChromeDiv extends HTMLButtonElement {
scrollIntoViewIfNeeded: () => void
}
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}
)
}
}
export interface WelcomeHeaderState {
version: string
}
export interface OniWithActiveSection extends Oni.Plugin.Api {
sessions: SessionManager
getActiveSection(): string
}
type ExecuteCommand = (command: string, args?: T) => void
export interface IWelcomeInputEvent {
select: boolean
vertical: number
horizontal?: number
}
interface ICommandMetadata {
execute: (args?: T) => void
command: string
}
export interface IWelcomeCommandsDictionary {
openFile: ICommandMetadata
openTutor: ICommandMetadata
openDocs: ICommandMetadata
openConfig: ICommandMetadata
openThemes: ICommandMetadata
openWorkspaceFolder: ICommandMetadata
commandPalette: ICommandMetadata
commandline: ICommandMetadata
restoreSession: (sessionName: string) => Promise
}
export class WelcomeBufferLayer implements Oni.BufferLayer {
public inputEvent = new Event()
constructor(private _oni: OniWithActiveSection) {}
public showCommandline = async () => {
const remapping: string = await this._oni.editors.activeEditor.neovim.callFunction(
"mapcheck",
[":", "n"],
)
const mapping = remapping || ":"
this._oni.automation.sendKeys(mapping)
}
public executeCommand: ExecuteCommand = (cmd, args) => {
this._oni.commands.executeCommand(cmd, args)
}
public restoreSession = async (name: string) => {
await this._oni.sessions.restoreSession(name)
}
// tslint:disable-next-line
public readonly welcomeCommands: IWelcomeCommandsDictionary = {
openFile: {
execute: args => this.executeCommand("oni.editor.newFile", args),
command: "oni.editor.newFile",
},
openWorkspaceFolder: {
execute: args => this.executeCommand("workspace.openFolder", args),
command: "workspace.openFolder",
},
commandPalette: {
execute: args => this.executeCommand("commands.show", args),
command: "commands.show",
},
commandline: {
execute: this.showCommandline,
command: "editor.executeVimCommand",
},
openTutor: {
execute: args => this.executeCommand("oni.tutor.open", args),
command: "oni.tutor.open",
},
openDocs: {
execute: args => this.executeCommand("oni.docs.open", args),
command: "oni.docs.open",
},
openConfig: {
execute: args => this.executeCommand("oni.config.openUserConfig", args),
command: "oni.config.openUserConfig",
},
openThemes: {
execute: args => this.executeCommand("oni.themes.choose", args),
command: "oni.themes.open",
},
restoreSession: args => this.restoreSession(args),
}
public get id() {
return "oni.welcome"
}
public get friendlyName() {
return "Welcome"
}
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({ vertical: 1, select: false })
break
case "k":
this.inputEvent.dispatch({ vertical: -1, select: false })
break
case "l":
this.inputEvent.dispatch({ vertical: 0, select: false, horizontal: 1 })
break
case "h":
this.inputEvent.dispatch({ vertical: 0, select: false, horizontal: -1 })
break
case "":
this.inputEvent.dispatch({ vertical: 0, select: true })
break
default:
this.inputEvent.dispatch({ vertical: 0, select: false })
}
}
public getProps() {
const active = this._oni.getActiveSection() === "editor"
const commandIds = Object.values(this.welcomeCommands)
.map(({ command }) => command)
.filter(Boolean)
const sessions = this._oni.sessions ? this._oni.sessions.allSessions : ([] as ISession[])
const sessionIds = sessions.map(({ id }) => id)
const ids = [...commandIds, ...sessionIds]
const sections = [commandIds.length, sessionIds.length].filter(Boolean)
return { active, ids, sections, sessions }
}
public render(context: Oni.BufferLayerRenderContext) {
const props = this.getProps()
return (
)
}
}
export interface WelcomeViewProps {
active: boolean
sessions: ISession[]
sections: number[]
ids: string[]
inputEvent: Event
commands: IWelcomeCommandsDictionary
getMetadata: () => Promise<{ version: string }>
restoreSession: (name: string) => Promise
executeCommand: ExecuteCommand
}
export interface WelcomeViewState {
version: string
selectedId: string
currentIndex: number
}
export class WelcomeView extends React.PureComponent {
public state: WelcomeViewState = {
version: null,
currentIndex: 0,
selectedId: this.props.ids[0],
}
private _welcomeElement = React.createRef()
public async componentDidMount() {
const metadata = await this.props.getMetadata()
this.setState({ version: metadata.version })
this.props.inputEvent.subscribe(this.handleInput)
}
public handleInput = async ({ vertical, select, horizontal }: IWelcomeInputEvent) => {
const { currentIndex } = this.state
const { sections, ids, active } = this.props
const newIndex = this.getNextIndex(currentIndex, vertical, horizontal, sections)
const selectedId = ids[newIndex]
this.setState({ currentIndex: newIndex, selectedId })
const selectedSession = this.props.sessions.find(session => session.id === selectedId)
if (select && active) {
if (selectedSession) {
await this.props.commands.restoreSession(selectedSession.name)
} else {
const currentCommand = this.getCurrentCommand(selectedId)
currentCommand.execute()
}
}
}
public getCurrentCommand(selectedId: string): ICommandMetadata {
const { commands } = this.props
const currentCommand = Object.values(commands).find(({ command }) => command === selectedId)
return currentCommand
}
public getNextIndex(
currentIndex: number,
vertical: number,
horizontal: number,
sections: number[],
) {
const nextPosition = currentIndex + vertical
const numberOfItems = this.props.ids.length
const multipleSections = sections.length > 1
// TODO: this currently handles *TWO* sections if more sections
// are to be added will need to rethink how to allow navigation across multiple sections
switch (true) {
case multipleSections && horizontal === 1:
return sections[0]
case multipleSections && horizontal === -1:
return 0
case nextPosition < 0:
return numberOfItems - 1
case nextPosition === numberOfItems:
return 0
default:
return nextPosition
}
}
public componentDidUpdate() {
if (this.props.active && this._welcomeElement && this._welcomeElement.current) {
this._welcomeElement.current.focus()
}
}
public render() {
const { version, selectedId } = this.state
return (
Oni
Modern Modal Editing
{version && {`v${version}`}}
{"https://onivim.io"}
Sessions
{this.props.sessions.length ? (
this.props.sessions.map(session => (
this.props.restoreSession(session.name)}
key={session.id}
>
{" "}
{session.name}
))
) : (
No Sessions Available
)}
)
}
}
export interface IWelcomeCommandsViewProps extends Partial {
selectedId: string
}
export const WelcomeCommandsView: React.SFC = props => {
const isSelected = (command: string) => command === props.selectedId
const { commands } = props
return (
Quick Commands
commands.openFile.execute()}
description="Control + N"
command={commands.openFile.command}
selected={isSelected(commands.openFile.command)}
/>
commands.openWorkspaceFolder.execute()}
command={commands.openWorkspaceFolder.command}
selected={isSelected(commands.openWorkspaceFolder.command)}
/>
commands.commandPalette.execute()}
description="Control + Shift + P"
command={commands.commandPalette.command}
selected={isSelected(commands.commandPalette.command)}
/>
commands.commandline.execute()}
selected={isSelected(commands.commandline.command)}
/>
Learn
commands.openTutor.execute()}
description="Learn modal editing with an interactive tutorial."
command={commands.openTutor.command}
selected={isSelected(commands.openTutor.command)}
/>
commands.openDocs.execute()}
description="Discover what Oni can do for you."
command={commands.openDocs.command}
selected={isSelected(commands.openDocs.command)}
/>
Customize
commands.openConfig.execute()}
description="Make Oni work the way you want."
command={commands.openConfig.command}
selected={isSelected(commands.openConfig.command)}
/>
commands.openThemes.execute()}
description="Choose a theme that works for you."
command={commands.openThemes.command}
selected={isSelected(commands.openThemes.command)}
/>
)
}
================================================
FILE: browser/src/Editor/NeovimEditor/index.ts
================================================
export * from "./NeovimEditor"
================================================
FILE: browser/src/Editor/NeovimEditor/markdown.ts
================================================
import { unescape } from "lodash"
import * as marked from "marked"
import * as Log from "oni-core-logging"
import { IGrammarPerLine, IGrammarToken } from "./../../Services/SyntaxHighlighting/TokenGenerator"
import * as DOMPurify from "dompurify"
const renderer = new marked.Renderer()
interface IRendererArgs {
tokens?: IGrammarPerLine
text: string
element?: TextElement
container?: TextElement
}
interface Symbols {
[symbol: string]: string[]
}
export const scopesToString = (scope: string[]) => {
if (scope) {
return scope
.map(s => {
if (s.includes(".")) {
const lastStop = s.lastIndexOf(".")
const remainder = s.substring(0, lastStop)
return remainder.replace(/\./g, "-")
}
return s
})
.filter(value => !!value)
.join(" ")
}
return null
}
/**
* escapeRegExp
* Escapes a string intended for use as a regexp
* @param {string} str
* @returns {string}
*/
export function escapeRegExp(str: string) {
// NOTE This does NOT escape the "|" operator as it's needed for the Reg Exp
// Also does not escape "\-" as hypenated tokens can be found
return str.replace(/[\[\]\/\{\}\(\)\*\+\?\.\\\^\$\n\r]/g, "\\$&")
}
type TextElement = "code" | "pre" | "p" | "span"
export const createContainer = (type: TextElement, content: string) => {
switch (type) {
case "pre":
return `${content}`
case "p":
return `<${type} class="marked-paragraph">${content}${type}>`
case "code":
default:
return content
}
}
interface WrapTokenArgs {
tokens: IGrammarToken[]
element: string
text: string
}
export function wrapTokens({ tokens, element, text }: WrapTokenArgs): string {
try {
const symbols: Symbols = tokens.reduce((acc, token) => {
const symbol = text.substring(token.range.start.character, token.range.end.character)
acc[symbol] = token.scopes
return acc
}, {})
const symbolNames = Object.keys(symbols)
const banned = ["\n", "\r", " ", "|"]
const filteredNames = symbolNames.filter(str => !banned.includes(str))
// Check if a word is alphabetical if so make sure to match full words only
// if not alphabetical escape the string
const wholeWordMatch = filteredNames.map(
str => (/^[A-Za-z]/.test(str) ? `\\b${str}\\b` : escapeRegExp(str)),
)
const symbolRegex = new RegExp("(" + wholeWordMatch.join("|") + ")", "g")
const html = text.replace(symbolRegex, (match, ...args) => {
const className = scopesToString(symbols[match])
return `<${element} class="marked ${className}">${match}${element}>`
})
return html
} catch (e) {
Log.warn(`Regex construction failed with: ${e.message}`)
return text
}
}
/**
* Takes a list of tokens which contain ranges, the text from marked (3rd party lib)
* uses a reg exp to replace all matching tokens with an element with a class that is styled
* else where
* @returns {string}
*/
export function renderWithClasses({
tokens,
text,
element = "span",
container = "p",
}: IRendererArgs) {
// This is critical because marked's renderer refuses to leave html untouched so it converts
// special chars to html entities
const unescapedText = unescape(text)
const whiteSpaceForCode = (line: string, code: boolean) => (code ? line.trim() : line)
if (tokens) {
const isCodeBlock = container === "code"
const tokenValues = Object.values(tokens)
const tokenLines = tokenValues.map(l => whiteSpaceForCode(l.line, isCodeBlock))
const parts = unescapedText.split("\n")
// Find common lines in lines to render and lines in tokenisation map
const intersection = tokenLines.filter(x => parts.includes(x))
const lineToToken = tokenValues.reduce((acc, t) => {
const key = whiteSpaceForCode(t.line, isCodeBlock)
acc[key] = t
return acc
}, {})
if (intersection.length) {
const html = intersection.reduce((acc, match) => {
return `${(acc += wrapTokens({
tokens: lineToToken[match].tokens,
element,
text: lineToToken[match].line,
}))}\n`
}, "")
if (container) {
return createContainer(container, html)
}
return html
}
}
return text
}
interface IConversionArgs {
markdown: string
tokens?: IGrammarPerLine
type?: string
}
const config = {
FORBID_TAGS: ["img", "script"],
}
/**
* Takes a markdown string and defines a custom renderer then for the element type and returns an html string
*
* @name convertMarkdown
* @function
* @param {string} {markdown A Markdown String
* @param {Object} tokens An Object of lines with tokens for each line
* @param {string} type the section of the quickfix being processed
* @returns {string} An html string
*/
export const convertMarkdown = ({ markdown, tokens, type = "title" }: IConversionArgs): string => {
marked.setOptions({
sanitize: true,
gfm: true,
renderer,
highlight: (code, lang) => {
return renderWithClasses({ text: code, tokens, container: "code" })
},
})
switch (type) {
case "documentation":
renderer.html = htmlString => DOMPurify.sanitize(htmlString, config)
renderer.paragraph = text => createContainer("p", DOMPurify.sanitize(text, config))
break
case "title":
default:
renderer.html = htmlString => DOMPurify.sanitize(htmlString, config)
renderer.paragraph = text => {
const stringWithClasses = renderWithClasses({ text, tokens })
return DOMPurify.sanitize(stringWithClasses, config)
}
renderer.blockquote = text => {
const stringWithClasses = renderWithClasses({
text,
tokens,
container: "pre",
})
return DOMPurify.sanitize(stringWithClasses, config)
}
}
const html = marked(markdown)
return html
}
================================================
FILE: browser/src/Editor/OniEditor/ColorHighlightLayer.tsx
================================================
import * as Color from "color"
import * as memoize from "lodash/memoize"
import * as Oni from "oni-api"
import * as Log from "oni-core-logging"
import * as React from "react"
import styled, { pixel, withProps } from "../../UI/components/common"
interface IBackground {
top: number
left: number
height: number
width: number
}
interface IHighlight {
color: string
fontFamily: string
height: number
fontSize: string
}
const Background = withProps(styled.div).attrs({
style: (props: IBackground) => ({
top: pixel(props.top),
left: pixel(props.left),
height: pixel(props.height),
width: pixel(props.width),
}),
})`
background-color: ${p => p.theme["editor.background"]};
position: absolute;
white-space: nowrap;
`
const HighlightSpan = withProps(styled.div)`
display: block;
height: 100%;
width: 100%;
color: ${p => (Color(p.color).dark() ? "white" : "black")};
font-family: ${p => p.fontFamily};
font-size: ${p => p.fontSize};
line-height: ${p => pixel(p.height + 5)}; /* vertically center text inside the highlight */
background-color: ${p => p.color};
`
interface IState {
error: Error
}
type IProps = IHighlight & IBackground
class Highlight extends React.PureComponent {
public state: IState = {
error: null,
}
public componentDidCatch(error: Error) {
this.setState({ error })
}
public render() {
return (
!this.state.error && (
{this.props.children}
)
)
}
}
export default class ColorHighlightLayer implements Oni.BufferLayer {
public render = memoize((context: Oni.BufferLayerRenderContext) => (
<>{this._getColorHighlights(context)}>
))
private readonly CSS_COLOR_NAMES = [
"AliceBlue",
"AntiqueWhite",
"Aqua",
"Aquamarine",
"Azure",
"Beige",
"Bisque",
"Black",
"BlanchedAlmond",
"Blue",
"BlueViolet",
"Brown",
"BurlyWood",
"CadetBlue",
"Chartreuse",
"Chocolate",
"Coral",
"CornflowerBlue",
"Cornsilk",
"Crimson",
"Cyan",
"DarkBlue",
"DarkCyan",
"DarkGoldenRod",
"DarkGray",
"DarkGrey",
"DarkGreen",
"DarkKhaki",
"DarkMagenta",
"DarkOliveGreen",
"Darkorange",
"DarkOrchid",
"DarkRed",
"DarkSalmon",
"DarkSeaGreen",
"DarkSlateBlue",
"DarkSlateGray",
"DarkSlateGrey",
"DarkTurquoise",
"DarkViolet",
"DeepPink",
"DeepSkyBlue",
"DimGray",
"DimGrey",
"DodgerBlue",
"FireBrick",
"FloralWhite",
"ForestGreen",
"Fuchsia",
"Gainsboro",
"GhostWhite",
"Gold",
"GoldenRod",
"Gray",
"Grey",
"Green",
"GreenYellow",
"HoneyDew",
"HotPink",
"IndianRed",
"Indigo",
"Ivory",
"Khaki",
"Lavender",
"LavenderBlush",
"LawnGreen",
"LemonChiffon",
"LightBlue",
"LightCoral",
"LightCyan",
"LightGoldenRodYellow",
"LightGray",
"LightGrey",
"LightGreen",
"LightPink",
"LightSalmon",
"LightSeaGreen",
"LightSkyBlue",
"LightSlateGray",
"LightSlateGrey",
"LightSteelBlue",
"LightYellow",
"Lime",
"LimeGreen",
"Linen",
"Magenta",
"Maroon",
"MediumAquaMarine",
"MediumBlue",
"MediumOrchid",
"MediumPurple",
"MediumSeaGreen",
"MediumSlateBlue",
"MediumSpringGreen",
"MediumTurquoise",
"MediumVioletRed",
"MidnightBlue",
"MintCream",
"MistyRose",
"Moccasin",
"NavajoWhite",
"Navy",
"OldLace",
"Olive",
"OliveDrab",
"Orange",
"OrangeRed",
"Orchid",
"PaleGoldenRod",
"PaleGreen",
"PaleTurquoise",
"PaleVioletRed",
"PapayaWhip",
"PeachPuff",
"Peru",
"Pink",
"Plum",
"PowderBlue",
"Purple",
"Red",
"RosyBrown",
"RoyalBlue",
"SaddleBrown",
"Salmon",
"SandyBrown",
"SeaGreen",
"SeaShell",
"Sienna",
"Silver",
"SkyBlue",
"SlateBlue",
"SlateGray",
"SlateGrey",
"Snow",
"SpringGreen",
"SteelBlue",
"Tan",
"Teal",
"Thistle",
"Tomato",
"Turquoise",
"Violet",
"Wheat",
"White",
"WhiteSmoke",
"Yellow",
"YellowGreen",
]
// Match hex/rgb/rgba/hsl/hsla colors -
// courtesy of https://gist.github.com/olmokramer/82ccce673f86db7cda5e
// the first section matches a hex code which can be 3 or 6 digits long the
// next section matches rgb or hsl value with an a optionally
// NB - the regex was tweaked so it could match inside a string
private _colorCodeRegex = /#(?:[0-9a-f]{3}){1,2}|(rgb|hsl)a?\((-?\d+%?[,\s]+){2,3}\s*[\d\.]+%?\)/gi
private _colorRegex: RegExp
private _fontSize: string
private _fontFamily: string
constructor(private _config: Oni.Configuration) {
this._fontSize = this._config.getValue("editor.fontSize")
this._fontFamily = this._config.getValue("editor.fontFamily")
this._config.onConfigurationChanged.subscribe(this._updateFontFamily)
this._constructRegex()
}
public get id() {
return "color-highlight"
}
public get friendlyName() {
return "CSS color highlight layer"
}
private _updateFontFamily = (configChanges: Partial) => {
const fontFamilyChanged = Object.keys(configChanges).includes("editor.fontFamily")
if (fontFamilyChanged) {
this._fontFamily = configChanges["editor.fontFamily"]
}
}
private _constructRegex() {
// Construct a regex checking for both color codes and all the different css colornames
const colorNames = this.CSS_COLOR_NAMES.map(name => `\\b${name}\\b`)
const colorNamesRegex = new RegExp("(" + colorNames.join("|") + ")")
this._colorRegex = new RegExp(
colorNamesRegex.source + "|" + this._colorCodeRegex.source,
"gi",
)
}
private _getColorHighlights = (context: Oni.BufferLayerRenderContext) => {
return context.visibleLines.map((line, idx) => {
try {
const matches = line.match(this._colorRegex)
if (matches) {
const colors = matches.filter(Boolean)
if (colors.length) {
const locations = colors.map(c => ({
color: c,
start: line.indexOf(c),
end: line.indexOf(c) + c.length,
}))
const currentLine = context.topBufferLine + idx - 1
return locations.map(location => {
const startPosition = context.bufferToPixel({
line: currentLine,
character: location.start,
})
const endPosition = context.bufferToPixel({
line: currentLine,
character: location.end,
})
if (!startPosition || !endPosition) {
return null
}
const width = endPosition.pixelX - startPosition.pixelX
return (
{location.color}
)
})
}
}
} catch (e) {
Log.warn(`Failed to create color highlights because ${e.message}`)
return null
}
return null
})
}
}
================================================
FILE: browser/src/Editor/OniEditor/ImageBufferLayer.tsx
================================================
/**
* ImageBufferLayer.tsx
*/
import * as React from "react"
import styled from "styled-components"
// import { inputManager, InputManager } from "./../../Services/InputManager"
import * as Oni from "oni-api"
import { withProps } from "./../../UI/components/common"
// import { VimNavigator } from "./../../UI/components/VimNavigator"
export class ImageBufferLayer implements Oni.BufferLayer {
constructor(private _buffer: Oni.Buffer) {}
public get id(): string {
return "oni.image"
}
public get friendlyName(): string {
return "Image"
}
public render(context: Oni.BufferLayerRenderContext): JSX.Element {
return
}
}
export interface IImageLayerViewProps {
imagePath: string
}
export interface IImageLayerViewState {
width: number
height: number
}
const ImageContainer = withProps<{}>(styled.div)`
background-color: ${props => props.theme["editor.background"]};
color: ${props => props.theme["editor.foreground"]};
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
opacity: 0.95;
& img {
max-width: 90%;
max-height: 90%;
padding-bottom: 2em;
}
`
export class ImageLayerView extends React.PureComponent<
IImageLayerViewProps,
IImageLayerViewState
> {
constructor(props: IImageLayerViewProps) {
super(props)
this.state = {
width: -1,
height: -1,
}
}
public componentDidMount(): void {
const image = new Image()
image.onload = () => {
this.setState({
width: image.width,
height: image.height,
})
}
image.src = this.props.imagePath
}
public render(): JSX.Element {
const dimensions = this.state ? `${this.state.width}x${this.state.height}` : ""
return (
{this.props.imagePath}
{dimensions}
)
}
}
================================================
FILE: browser/src/Editor/OniEditor/IndentGuideBufferLayer.tsx
================================================
import * as React from "react"
import * as detectIndent from "detect-indent"
import * as flatten from "lodash/flatten"
import * as last from "lodash/last"
import * as memoize from "lodash/memoize"
import * as Oni from "oni-api"
import { IBuffer } from "../BufferManager"
import styled, { pixel, withProps } from "./../../UI/components/common"
interface IWrappedLine {
start: number
end: number
line: string
}
interface IProps {
height: number
left: number
top: number
color?: string
}
interface ConfigOptions {
skipFirst: boolean
color?: string
}
interface LinePropsWithLevels extends IndentLinesProps {
levelOfIndentation: number
}
interface IndentLinesProps {
top: number
left: number
height: number
line: string
indentBy: number
indentSize: number
characterWidth: number
}
const Container = styled.div``
const IndentLine = withProps(styled.span).attrs({
style: ({ height, left, top }: IProps) => ({
height: pixel(height),
left: pixel(left),
top: pixel(top),
}),
})`
border-left: 1px solid ${p => p.color || "rgba(100, 100, 100, 0.4)"};
position: absolute;
`
interface IndentLayerArgs {
buffer: IBuffer
configuration: Oni.Configuration
}
class IndentGuideBufferLayer implements Oni.BufferLayer {
public render = memoize((bufferLayerContext: Oni.BufferLayerRenderContext) => {
return {this._renderIndentLines(bufferLayerContext)}
})
private _buffer: IBuffer
private _userSpacing: number
private _configuration: Oni.Configuration
constructor({ buffer, configuration }: IndentLayerArgs) {
this._buffer = buffer
this._configuration = configuration
this._userSpacing = this._buffer.shiftwidth || this._buffer.tabstop
}
get id() {
return "indent-guides"
}
get friendlyName() {
return "Indent Guide Lines"
}
private _getIndentLines = (guidePositions: IndentLinesProps[], options: ConfigOptions) => {
return flatten(
guidePositions.map((props, idx) => {
const indents: JSX.Element[] = []
// Create a line per indentation
for (
let levelOfIndentation = 0;
levelOfIndentation < props.indentBy;
levelOfIndentation++
) {
const lineProps = { ...props, levelOfIndentation }
const adjustedLeft = this._calculateLeftPosition(lineProps)
const shouldSkip = this._determineIfShouldSkip(lineProps, options)
const key = `${props.line.trim()}-${idx}-${levelOfIndentation}`
indents.push(
!shouldSkip && (
),
)
}
return indents
}),
)
}
private _determineIfShouldSkip(props: LinePropsWithLevels, options: ConfigOptions) {
const skipFirstIndentLine =
options.skipFirst && props.levelOfIndentation === props.indentBy - 1
return skipFirstIndentLine
}
/**
* Remove one indent from left positioning and move lines slightly inwards -
* by a third of a character for a better visual appearance
*/
private _calculateLeftPosition(props: LinePropsWithLevels) {
const adjustedLeft =
props.left -
props.indentSize -
props.levelOfIndentation * props.indentSize +
props.characterWidth / 3
return adjustedLeft
}
private _getWrappedLines(context: Oni.BufferLayerRenderContext): IWrappedLine[] {
const { lines } = context.visibleLines.reduce(
(acc, line, index) => {
const currentLine = context.topBufferLine + index
const bufferInfo = context.bufferToScreen({ line: currentLine, character: 0 })
if (bufferInfo && bufferInfo.screenY) {
const { screenY: screenLine } = bufferInfo
if (acc.expectedLine !== screenLine) {
acc.lines.push({
start: acc.expectedLine,
end: screenLine,
line,
})
acc.expectedLine = screenLine + 1
} else {
acc.expectedLine += 1
}
}
return acc
},
{ lines: [], expectedLine: 1 },
)
return lines
}
private _regulariseIndentation(indentation: detectIndent.IndentInfo) {
const isOddBy = indentation.amount % this._userSpacing
const amountToIndent = isOddBy ? indentation.amount - isOddBy : indentation.amount
return amountToIndent
}
/**
* Calculates the position of each indent guide element using shiftwidth or tabstop if no
* shift width available
* @name _renderIndentLines
* @function
* @param {Oni.BufferLayerRenderContext} bufferLayerContext The buffer layer context
* @returns {JSX.Element[]} An array of react elements
*/
private _renderIndentLines = (bufferLayerContext: Oni.BufferLayerRenderContext) => {
// TODO: If the beginning of the visible lines is wrapping no lines are drawn
const wrappedScreenLines = this._getWrappedLines(bufferLayerContext)
const options = {
color: this._configuration.getValue("experimental.indentLines.color"),
skipFirst: this._configuration.getValue("experimental.indentLines.skipFirst"),
}
const { visibleLines, fontPixelHeight, fontPixelWidth, topBufferLine } = bufferLayerContext
const indentSize = this._userSpacing * fontPixelWidth
const { allIndentations } = visibleLines.reduce(
(acc, line, currenLineNumber) => {
const rawIndentation = detectIndent(line)
const regularisedIndent = this._regulariseIndentation(rawIndentation)
const previous = last(acc.allIndentations)
const height = Math.ceil(fontPixelHeight)
// start position helps determine the initial indent offset
const startPosition = bufferLayerContext.bufferToScreen({
line: topBufferLine,
character: regularisedIndent,
})
const wrappedLine = wrappedScreenLines.find(wrapped => wrapped.line === line)
const levelsOfWrapping = wrappedLine ? wrappedLine.end - wrappedLine.start : 1
const adjustedHeight = height * levelsOfWrapping
if (!startPosition) {
return acc
}
const { pixelX: left, pixelY: top } = bufferLayerContext.screenToPixel({
screenX: startPosition.screenX,
screenY: currenLineNumber,
})
const adjustedTop = top + acc.wrappedHeightAdjustment
// Only adjust height for Subsequent lines!
if (wrappedLine) {
acc.wrappedHeightAdjustment += adjustedHeight
}
if (!line && previous) {
acc.allIndentations.push({
...previous,
line,
top: adjustedTop,
})
return acc
}
const indent = {
left,
line,
indentSize,
top: adjustedTop,
height: adjustedHeight,
characterWidth: fontPixelWidth,
indentBy: regularisedIndent / this._userSpacing,
}
acc.allIndentations.push(indent)
return acc
},
{ allIndentations: [], wrappedHeightAdjustment: 0 },
)
return this._getIndentLines(allIndentations, options)
}
}
export default IndentGuideBufferLayer
================================================
FILE: browser/src/Editor/OniEditor/OniEditor.tsx
================================================
/**
* OniEditor.ts
*
* Editor implementation for Oni
*
* Extends the capabilities of the NeovimEditor
*/
import * as path from "path"
import * as React from "react"
import * as types from "vscode-languageserver-types"
import * as Oni from "oni-api"
import * as Log from "oni-core-logging"
import { IEvent } from "oni-types"
// import { remote } from "electron"
import * as App from "./../../App"
import * as Utility from "./../../Utility"
import { PluginManager } from "./../../Plugins/PluginManager"
import { IColors } from "./../../Services/Colors"
import { commandManager } from "./../../Services/CommandManager"
import { CompletionProviders } from "./../../Services/Completion"
import { Configuration } from "./../../Services/Configuration"
import { IDiagnosticsDataSource } from "./../../Services/Diagnostics"
import { editorManager } from "./../../Services/EditorManager"
import { LanguageManager } from "./../../Services/Language"
import { MenuManager } from "./../../Services/Menu"
import { OverlayManager } from "./../../Services/Overlay"
import { SnippetManager } from "./../../Services/Snippets"
import { ISyntaxHighlighter } from "./../../Services/SyntaxHighlighting"
import { ThemeManager } from "./../../Services/Themes"
import { TokenColors } from "./../../Services/TokenColors"
import { Workspace } from "./../../Services/Workspace"
import { BufferScrollBarContainer } from "./containers/BufferScrollBarContainer"
import { DefinitionContainer } from "./containers/DefinitionContainer"
import { ErrorsContainer } from "./containers/ErrorsContainer"
import { NeovimEditor } from "./../NeovimEditor"
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"
// Helper method to wrap a react component into a layer
const wrapReactComponentWithLayer = (id: string, component: JSX.Element): Oni.BufferLayer => {
return {
id,
render: (context: Oni.BufferLayerRenderContext) => (context.isActive ? component : null),
}
}
export class OniEditor extends Utility.Disposable implements Oni.Editor {
private _neovimEditor: NeovimEditor
public get mode(): string {
return this._neovimEditor.mode
}
public get onCursorMoved(): IEvent {
return this._neovimEditor.onCursorMoved
}
public get onModeChanged(): IEvent {
return this._neovimEditor.onModeChanged
}
public get onBufferEnter(): IEvent {
return this._neovimEditor.onBufferEnter
}
public get onBufferLeave(): IEvent {
return this._neovimEditor.onBufferLeave
}
public get onBufferChanged(): IEvent {
return this._neovimEditor.onBufferChanged
}
public get onBufferSaved(): IEvent {
return this._neovimEditor.onBufferSaved
}
public get onBufferScrolled(): IEvent {
return this._neovimEditor.onBufferScrolled
}
public get /* override */ activeBuffer(): Oni.Buffer {
return this._neovimEditor.activeBuffer
}
public get onQuit(): IEvent {
return this._neovimEditor.onNeovimQuit
}
// Capabilities
public get neovim(): Oni.NeovimEditorCapability {
return this._neovimEditor.neovim
}
public get syntaxHighlighter(): ISyntaxHighlighter {
return this._neovimEditor.syntaxHighlighter
}
constructor(
private _colors: IColors,
private _completionProviders: CompletionProviders,
private _configuration: Configuration,
private _diagnostics: IDiagnosticsDataSource,
private _languageManager: LanguageManager,
private _menuManager: MenuManager,
private _overlayManager: OverlayManager,
private _pluginManager: PluginManager,
private _snippetManager: SnippetManager,
private _themeManager: ThemeManager,
private _tokenColors: TokenColors,
private _workspace: Workspace,
) {
super()
this._neovimEditor = new NeovimEditor(
this._colors,
this._completionProviders,
this._configuration,
this._diagnostics,
this._languageManager,
this._menuManager,
this._overlayManager,
this._pluginManager,
this._snippetManager,
this._themeManager,
this._tokenColors,
this._workspace,
)
editorManager.registerEditor(this)
this.trackDisposable(
this._neovimEditor.onNeovimQuit.subscribe(() => {
const handle = windowManager.getSplitHandle(this)
handle.close()
editorManager.unregisterEditor(this)
this.dispose()
}),
)
this.trackDisposable(
App.registerQuitHook(async () => {
if (!this.isDisposed) {
this.quit()
}
}),
)
this._neovimEditor.bufferLayers.addBufferLayer("*", buf =>
wrapReactComponentWithLayer("oni.layer.scrollbar", ),
)
this._neovimEditor.bufferLayers.addBufferLayer("*", buf =>
wrapReactComponentWithLayer("oni.layer.definition", ),
)
this._neovimEditor.bufferLayers.addBufferLayer("*", buf =>
wrapReactComponentWithLayer("oni.layer.errors", ),
)
const imageExtensions = this._configuration.getValue("editor.imageLayerExtensions")
const bannedIndentExtensions = this._configuration.getValue(
"experimental.indentLines.bannedFiletypes",
)
this._neovimEditor.bufferLayers.addBufferLayer(
buf => imageExtensions.includes(path.extname(buf.filePath)),
buf => new ImageBufferLayer(buf),
)
if (this._configuration.getValue("experimental.indentLines.enabled")) {
this._neovimEditor.bufferLayers.addBufferLayer(
buf => {
const extension = path.extname(buf.filePath)
return extension && !bannedIndentExtensions.includes(extension)
},
buffer =>
new IndentLineBufferLayer({
buffer: buffer as IBuffer,
configuration: this._configuration,
}),
)
}
if (this._configuration.getValue("experimental.colorHighlight.enabled")) {
this._neovimEditor.bufferLayers.addBufferLayer(
buf =>
this._configuration
.getValue("experimental.colorHighlight.filetypes")
.includes(path.extname(buf.filePath)),
_buf => new ColorHighlightLayer(this._configuration),
)
}
this._neovimEditor.onShowWelcomeScreen.subscribe(this.openWelcomeScreen)
}
public dispose(): void {
super.dispose()
if (this._neovimEditor) {
this._neovimEditor.dispose()
this._neovimEditor = null
}
}
public enter(): void {
Log.info("[OniEditor::enter]")
editorManager.setActiveEditor(this)
this._neovimEditor.enter()
commandManager.registerCommand({
name: "Oni: Show Welcome",
detail: "Open the welcome screen",
command: "oni.welcome.open",
execute: this.openWelcomeScreen,
enabled: () => this._configuration.getValue("experimental.welcome.enabled"),
})
commandManager.registerCommand({
command: "editor.split.horizontal",
execute: () => this._split("horizontal"),
enabled: () => editorManager.activeEditor === this,
name: null,
detail: null,
})
commandManager.registerCommand({
command: "editor.split.vertical",
execute: () => this._split("vertical"),
enabled: () => editorManager.activeEditor === this,
name: null,
detail: null,
})
}
public leave(): void {
Log.info("[OniEditor::leave]")
this._neovimEditor.leave()
}
public openWelcomeScreen = async () => {
const oni = this._pluginManager.getApi()
const welcomeBuffer = await this._neovimEditor.createWelcomeBuffer()
const welcomeLayer = new WelcomeBufferLayer(oni as OniWithActiveSection)
welcomeBuffer.addLayer(welcomeLayer)
}
public async openFile(
file: string,
openOptions: Oni.FileOpenOptions = Oni.DefaultFileOpenOptions,
): Promise {
const openMode = openOptions.openMode
if (this._configuration.getValue("editor.split.mode") === "oni") {
if (
openMode === Oni.FileOpenMode.HorizontalSplit ||
openMode === Oni.FileOpenMode.VerticalSplit
) {
const splitDirection =
openMode === Oni.FileOpenMode.HorizontalSplit ? "horizontal" : "vertical"
const newEditor = await this._split(splitDirection)
return newEditor.openFile(file, { openMode: Oni.FileOpenMode.Edit })
}
}
return this._neovimEditor.openFile(file, openOptions)
}
public async newFile(filePath: string): Promise {
return this._neovimEditor.newFile(filePath)
}
public async clearSelection(): Promise {
return this._neovimEditor.clearSelection()
}
public async setSelection(range: types.Range): Promise {
return this._neovimEditor.setSelection(range)
}
public async setTextOptions(textOptions: Oni.EditorTextOptions): Promise {
return this._neovimEditor.setTextOptions(textOptions)
}
public async blockInput(
inputFunction: (input: Oni.InputCallbackFunction) => Promise,
): Promise {
return this._neovimEditor.blockInput(inputFunction)
}
public executeCommand(command: string): void {
this._neovimEditor.executeCommand(command)
}
public restoreSession(sessionDetails: ISession) {
return this._neovimEditor.restoreSession(sessionDetails)
}
public getCurrentSession() {
return this._neovimEditor.getCurrentSession()
}
public persistSession(sessionDetails: ISession) {
return this._neovimEditor.persistSession(sessionDetails)
}
public getBuffers(): Array {
return this._neovimEditor.getBuffers()
}
public async bufferDelete(bufferId: string = this.activeBuffer.id): Promise {
this._neovimEditor.bufferDelete(bufferId)
}
public async init(filesToOpen: string[]): Promise {
Log.info("[OniEditor::init] Called with filesToOpen: " + filesToOpen)
return this._neovimEditor.init(filesToOpen)
}
public async input(key: string): Promise {
return this._neovimEditor.input(key)
}
public render(): JSX.Element {
return this._neovimEditor.render()
}
public async quit(): Promise {
return this._neovimEditor.quit()
}
private async _split(direction: SplitDirection): Promise {
if (this._configuration.getValue("editor.split.mode") !== "oni") {
if (direction === "horizontal") {
await this._neovimEditor.neovim.command(":sp")
} else {
await this._neovimEditor.neovim.command(":vsp")
}
return this
}
const newEditor = new OniEditor(
this._colors,
this._completionProviders,
this._configuration,
this._diagnostics,
this._languageManager,
this._menuManager,
this._overlayManager,
this._pluginManager,
this._snippetManager,
this._themeManager,
this._tokenColors,
this._workspace,
)
windowManager.createSplit(direction, newEditor, this)
await newEditor.init([])
return newEditor
}
}
================================================
FILE: browser/src/Editor/OniEditor/containers/BufferScrollBarContainer.ts
================================================
import * as types from "vscode-languageserver-types"
import { connect } from "react-redux"
import { createSelector } from "reselect"
import { getColorFromSeverity } from "./../../../Services/Diagnostics"
import {
BufferScrollBar,
IBufferScrollBarProps,
IScrollBarMarker,
} from "./../../../UI/components/BufferScrollBar"
import * as Selectors from "./../../NeovimEditor/NeovimEditorSelectors"
import * as State from "./../../NeovimEditor/NeovimEditorStore"
export const getCurrentLine = createSelector([Selectors.getActiveWindow], activeWindow => {
return activeWindow.line
})
const NoScrollBar: IBufferScrollBarProps = {
windowId: null,
bufferSize: -1,
height: -1,
windowTopLine: -1,
windowBottomLine: -1,
markers: [],
visible: false,
}
export const shouldIncludeCursorMarker = (state: State.IState) => {
return state.configuration["editor.scrollBar.cursorTick.visible"]
}
export const getMarkers = createSelector(
[getCurrentLine, Selectors.getErrorsForActiveFile, shouldIncludeCursorMarker],
(activeLine, fileErrors, includeCursor) => {
const errorMarkers = fileErrors.map((e: types.Diagnostic) => ({
line: e.range.start.line || 0,
height: 1,
color: getColorFromSeverity(e.severity),
}))
if (!includeCursor) {
return errorMarkers
} else {
const cursorMarker: IScrollBarMarker = {
line: activeLine - 1,
height: 1,
color: "rgb(200, 200, 200)",
}
return [...errorMarkers, cursorMarker]
}
},
)
const mapStateToProps = (state: State.IState): IBufferScrollBarProps => {
const visible = state.configuration["editor.scrollBar.visible"]
const activeWindow = Selectors.getActiveWindow(state)
if (!activeWindow) {
return NoScrollBar
}
const dimensions = Selectors.getActiveWindowPixelDimensions(state)
const file = activeWindow.file
const buffer = Selectors.getActiveBuffer(state)
if (file === null || !buffer) {
return NoScrollBar
}
const bufferSize = buffer.totalLines
const markers = getMarkers(state)
return {
windowId: activeWindow.windowId,
windowTopLine: activeWindow.topBufferLine,
windowBottomLine: activeWindow.bottomBufferLine,
bufferSize,
markers,
height: dimensions.height,
visible,
}
}
export const BufferScrollBarContainer = connect(mapStateToProps)(BufferScrollBar)
================================================
FILE: browser/src/Editor/OniEditor/containers/DefinitionContainer.ts
================================================
import { connect } from "react-redux"
import * as types from "vscode-languageserver-types"
import { Definition, IDefinitionProps } from "./../../../UI/components/Definition"
import * as Selectors from "./../../NeovimEditor/NeovimEditorSelectors"
import * as State from "./../../NeovimEditor/NeovimEditorStore"
const emptyRange = types.Range.create(types.Position.create(-1, -1), types.Position.create(-1, -1))
const getActiveDefinition = (state: State.IState) => state.definition
const mapStateToProps = (state: State.IState): IDefinitionProps => {
const window = Selectors.getActiveWindow(state)
const noop = (): any => null
const activeDefinition = getActiveDefinition(state)
const range = activeDefinition ? activeDefinition.token.range : emptyRange
return {
color: state.colors["editor.foreground"],
range,
fontWidthInPixels: state.fontPixelWidth,
fontHeightInPixels: state.fontPixelHeight,
bufferToScreen: window ? window.bufferToScreen : noop,
screenToPixel: window ? window.screenToPixel : noop,
}
}
export const DefinitionContainer = connect(mapStateToProps)(Definition)
================================================
FILE: browser/src/Editor/OniEditor/containers/ErrorsContainer.ts
================================================
import { connect } from "react-redux"
import { Errors, IErrorsProps } from "./../../../UI/components/Error"
import * as Selectors from "./../../NeovimEditor/NeovimEditorSelectors"
import * as State from "./../../NeovimEditor/NeovimEditorStore"
const mapStateToProps = (state: State.IState): IErrorsProps => {
const window = Selectors.getActiveWindow(state)
const errors = Selectors.getErrorsForActiveFile(state)
const noop = (): any => null
return {
errors,
fontWidthInPixels: state.fontPixelWidth,
fontHeightInPixels: state.fontPixelHeight,
bufferToScreen: window ? window.bufferToScreen : noop,
screenToPixel: window ? window.screenToPixel : noop,
}
}
export const ErrorsContainer = connect(mapStateToProps)(Errors)
================================================
FILE: browser/src/Editor/OniEditor/index.ts
================================================
export * from "./OniEditor"
================================================
FILE: browser/src/Font.ts
================================================
export const FallbackFonts =
"Consolas,Monaco,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace"
export interface IFontMeasurement {
width: number
height: number
}
export function measureFont(
fontFamily: string,
fontSize: string,
fontWeight: string,
characterToTest = "H",
) {
const div = document.createElement("div")
div.style.position = "absolute"
div.style.left = "10px"
div.style.top = "10px"
div.style.backgroundColor = "red"
div.style.left = "-1000px"
div.style.top = "-1000px"
div.textContent = characterToTest
div.style.fontFamily = `${fontFamily},${FallbackFonts}`
div.style.fontSize = fontSize
div.style.fontWeight = fontWeight
const isItalicAvailable = isStyleAvailable(fontFamily, "italic", fontSize)
const isBoldAvailable = isStyleAvailable(fontFamily, "bold", fontSize)
document.body.appendChild(div)
const rect = div.getBoundingClientRect()
const width = rect.width
const height = rect.height
document.body.removeChild(div)
return {
width,
height,
isItalicAvailable,
isBoldAvailable,
}
}
export function addDefaultUnitIfNeeded(fontSize: string) {
const roundFont = `${Math.round(parseFloat(fontSize))}px`
return roundFont
}
export function isStyleAvailable(fontName: string, style: string, fontSize = "12px") {
const text = "abcdefghijklmnopqrstuvwxyz0123456789"
let canvas = document.createElement("canvas")
const context = canvas.getContext("2d")
context.font = `${fontSize} ${fontName}`
const baselineSize = context.measureText(text).width
context.font = `${style} ${fontSize} ${fontName}`
const newSize = context.measureText(text).width
canvas = null
return newSize === baselineSize
}
================================================
FILE: browser/src/Grid.ts
================================================
export class Grid {
private _cells: any = {}
private _width: number = 0
private _height: number = 0
public get width(): number {
return this._width
}
public get height(): number {
return this._height
}
public getCell(x: number, y: number): null | T {
const row = this._cells[y]
if (!row) {
return null
}
const col = row[x]
if (typeof col === "undefined") {
return null
}
return col
}
public setCell(x: number, y: number, val: T | null) {
let row = this._cells[y]
row = row || {}
row[x] = val
this._cells[y] = row
if (x >= this._width) {
this._width = x + 1
}
if (y >= this._height) {
this._height = y + 1
}
}
public clear(): void {
this._cells = {}
this._width = 0
this._height = 0
}
public shiftRows(rowsToShift: number): void {
// var val = typeof defaultVal === "undefined" ? null : defaultVal
let dir: any
let start: any
if (rowsToShift >= 0) {
dir = 1
start = 0
} else {
dir = -1
start = this._height - 1
}
let current = start
while (current >= 0 && current < this._height) {
const srcRow = current + rowsToShift
for (let x = 0; x < this._width; x++) {
const oldCell = this.getCell(x, srcRow)
this.setCell(x, current, oldCell as any)
}
current += dir
}
}
public setRegionFromGrid(grid: Grid, xPosition: number, yPosition: number): void {
for (let x = 0; x < grid.width; x++) {
for (let y = 0; y < grid.height; y++) {
const sourceCell = grid.getCell(x, y)
this.setCell(xPosition + x, yPosition + y, sourceCell)
}
}
}
public setRegion(
startX: number,
startY: number,
width: number,
height: number,
val?: T | null,
): void {
const valToSet = typeof val === "undefined" ? null : val
for (let x = startX; x < startX + width; x++) {
for (let y = startY; y < startY + height; y++) {
this.setCell(x, y, valToSet)
}
}
}
public cloneRegion(x: number, y: number, width: number, height: number): Grid {
const outputGrid = new Grid()
for (let cloneX = 0; cloneX < width; cloneX++) {
for (let cloneY = 0; cloneY < height; cloneY++) {
const sourceCell = this.getCell(cloneX + x, cloneY + y)
outputGrid.setCell(cloneX, cloneY, sourceCell)
}
}
return outputGrid
}
}
================================================
FILE: browser/src/Input/KeyBindings.ts
================================================
/**
* KeyBindings.ts
*
* Default, out-of-the-box keybindings for Oni
*/
import * as Oni from "oni-api"
import * as Platform from "./../Platform"
import { Configuration } from "./../Services/Configuration"
interface ISidebar {
sidebar: {
activeEntryId: string
isFocused: boolean
}
}
export const applyDefaultKeyBindings = (oni: Oni.Plugin.Api, config: Configuration): void => {
const { editors, input, menu } = oni
input.unbindAll()
const isVisualMode = () => editors.activeEditor.mode === "visual"
const isNormalMode = () => editors.activeEditor.mode === "normal"
const isNotInsertMode = () => editors.activeEditor.mode !== "insert"
const isInsertOrCommandMode = () =>
editors.activeEditor.mode === "insert" || editors.activeEditor.mode === "cmdline_normal"
const oniWithSidebar = oni as Oni.Plugin.Api & ISidebar
const isSidebarPaneOpen = (paneId: string) =>
oniWithSidebar.sidebar.activeEntryId === paneId &&
oniWithSidebar.sidebar.isFocused &&
!isInsertOrCommandMode() &&
!isMenuOpen()
const isExplorerActive = () => isSidebarPaneOpen("oni.sidebar.explorer")
const areSessionsActive = () => isSidebarPaneOpen("oni.sidebar.sessions")
const isVCSActive = () => isSidebarPaneOpen("oni.sidebar.vcs")
const isMenuOpen = () => menu.isMenuOpen()
if (Platform.isMac()) {
input.bind("", "oni.quit")
input.bind("", "quickOpen.show", () => isNormalMode() && !isMenuOpen())
input.bind("", "commands.show", isNormalMode)
input.bind("", "language.codeAction.expand")
input.bind("", "language.symbols.workspace", () => !menu.isMenuOpen())
input.bind("", "language.symbols.document")
input.bind("", "oni.editor.minimize")
input.bind("", "oni.editor.hide")
input.bind("", "buffer.toggle")
input.bind("", "search.searchAllFiles")
input.bind("", "explorer.toggle")
input.bind("", "sidebar.decreaseWidth")
input.bind("", "sidebar.increaseWidth")
input.bind("", "oni.config.openConfigJs")
if (config.getValue("editor.clipboard.enabled")) {
input.bind("", "editor.clipboard.yank", isVisualMode)
input.bind("", "editor.clipboard.paste", isInsertOrCommandMode)
}
// Browser
input.bind("", "browser.goBack")
input.bind("", "browser.goForward")
input.bind("", "browser.reload")
} else {
input.bind("", "oni.quit")
input.bind("", "sidebar.decreaseWidth")
input.bind("", "sidebar.increaseWidth")
input.bind("", "quickOpen.show", () => isNormalMode() && !isMenuOpen())
input.bind("", "commands.show", isNormalMode)
input.bind("", "language.codeAction.expand")
input.bind("", "language.symbols.workspace", () => !menu.isMenuOpen())
input.bind("", "language.symbols.document")
input.bind("", "buffer.toggle")
input.bind("", "search.searchAllFiles")
input.bind("", "explorer.toggle")
input.bind("", "oni.config.openConfigJs")
if (config.getValue("editor.clipboard.enabled")) {
input.bind("", "editor.clipboard.yank", isVisualMode)
input.bind("", "editor.clipboard.paste", isInsertOrCommandMode)
}
// Browser
input.bind("", "browser.goBack")
input.bind("", "browser.goForward")
input.bind("", "browser.reload")
}
input.bind("", "editor.rename", () => isNormalMode())
input.bind("", "language.format")
input.bind([""], "language.gotoDefinition", () => isNormalMode() && !menu.isMenuOpen())
input.bind(
["", ""],
"language.gotoDefinition.openVertical",
() => isNormalMode() && !menu.isMenuOpen(),
)
input.bind(
["", ""],
"language.gotoDefinition.openHorizontal",
() => isNormalMode() && !menu.isMenuOpen(),
)
input.bind("", "commands.show", isNormalMode)
input.bind("", "oni.process.cyclePrevious")
input.bind("", "oni.process.cycleNext")
// QuickOpen
input.bind("", "quickOpen.showBufferLines", isNormalMode)
input.bind([""], "quickOpen.openFileVertical")
input.bind([""], "quickOpen.openFileHorizontal")
input.bind("", "quickOpen.openFileNewTab")
input.bind([""], "quickOpen.openFileAlternative")
// Snippets
input.bind("", "snippet.nextPlaceholder")
input.bind("", "snippet.previousPlaceholder")
input.bind("", "snippet.cancel")
// Completion
input.bind([""], "contextMenu.select")
input.bind(["", ""], "contextMenu.next")
input.bind(["", ""], "contextMenu.previous")
input.bind(
[""],
"contextMenu.close",
isNotInsertMode /* In insert mode, the mode change will close the popupmenu anyway */,
)
// Menu
input.bind(["", ""], "menu.next")
input.bind(["", ""], "menu.previous")
input.bind(["", "