From 39477a2999c5f269126f4d74d711fabb4885afc9 Mon Sep 17 00:00:00 2001 From: Ryan Leavengood Date: Tue, 10 Mar 2020 16:14:56 -0400 Subject: [PATCH] Implement the next/previous editor commands and keybindings (#1447) * Implement the next/previous editor commands and keybindings This wraps around like VSCode. Fixes #778 and part of #1423. * Remove unneeded new actions * Adjust how the next/previous editor code works in EditorGroup Resolve the TODO for _getIndexOfElement by making it return option(int). List.find_opt is not actually the right choice to get an index. Also ran `esy format`. --- src/Model/EditorGroup.re | 59 +++++++++++++++++++++++--- src/Model/EditorGroup.rei | 2 + src/Model/EditorGroupReducer.re | 3 ++ src/Store/CommandStoreConnector.re | 12 ++++++ src/Store/KeyBindingsStoreConnector.re | 20 +++++++++ src/Store/VimStoreConnector.re | 8 ++++ 6 files changed, 99 insertions(+), 5 deletions(-) diff --git a/src/Model/EditorGroup.re b/src/Model/EditorGroup.re index 32005ad25a..16090e2f3f 100644 --- a/src/Model/EditorGroup.re +++ b/src/Model/EditorGroup.re @@ -61,16 +61,23 @@ let getOrCreateEditorForBuffer = (state, bufferId) => { }; }; -// TODO: Just use List.find_opt? let rec _getIndexOfElement = elem => fun - | [] => (-1) - | [hd, ...tl] => hd === elem ? 0 : _getIndexOfElement(elem, tl) + 1; + | [] => None + | [hd, ...tl] => + hd === elem + ? Some(0) + : ( + switch (_getIndexOfElement(elem, tl)) { + | None => None + | Some(i) => Some(i + 1) + } + ); let _getAdjacentEditor = (editor: int, reverseTabOrder: list(int)) => { switch (_getIndexOfElement(editor, reverseTabOrder)) { - | (-1) => None - | idx => + | None => None + | Some(idx) => switch ( List.nth_opt(reverseTabOrder, idx + 1), List.nth_opt(reverseTabOrder, max(idx - 1, 0)), @@ -82,6 +89,48 @@ let _getAdjacentEditor = (editor: int, reverseTabOrder: list(int)) => { }; }; +let setActiveEditorTo = (kind, model) => + switch (model.reverseTabOrder) { + | [] + | [_] => model + | _ => + switch (model.activeEditorId) { + | Some(activeEditorId) => + switch (_getIndexOfElement(activeEditorId, model.reverseTabOrder)) { + | None => model + | Some(idx) => + // The diff amounts are inverted because the list is in reverse order + let newIndex = + switch (kind) { + | `Next => idx - 1 + | `Previous => idx + 1 + }; + + let count = List.length(model.reverseTabOrder); + + let newIndex = + if (newIndex < 0) { + // Wrapping negative, go to end + count - 1; + } else if (newIndex >= count) { + 0; + // If this is past the end, go to zero + } else { + newIndex; + }; + + { + ...model, + activeEditorId: List.nth_opt(model.reverseTabOrder, newIndex), + }; + } + | None => model + } + }; + +let nextEditor = setActiveEditorTo(`Next); +let previousEditor = setActiveEditorTo(`Previous); + let isEmpty = model => IntMap.is_empty(model.editors); let removeEditorById = (state, editorId) => { diff --git a/src/Model/EditorGroup.rei b/src/Model/EditorGroup.rei index 614b92d629..84f33d3993 100644 --- a/src/Model/EditorGroup.rei +++ b/src/Model/EditorGroup.rei @@ -17,6 +17,8 @@ let getActiveEditor: t => option(Feature_Editor.Editor.t); let setActiveEditor: (t, int) => t; let getEditorById: (int, t) => option(Feature_Editor.Editor.t); let getOrCreateEditorForBuffer: (t, int) => (t, Feature_Editor.EditorId.t); +let nextEditor: t => t; +let previousEditor: t => t; let removeEditorById: (t, int) => t; let isEmpty: t => bool; diff --git a/src/Model/EditorGroupReducer.re b/src/Model/EditorGroupReducer.re index 9e15980f4a..87cb77c786 100644 --- a/src/Model/EditorGroupReducer.re +++ b/src/Model/EditorGroupReducer.re @@ -24,6 +24,9 @@ let reduce = (v: EditorGroup.t, action: Actions.t) => { let (newState, activeEditorId) = EditorGroup.getOrCreateEditorForBuffer(v, id); {...newState, activeEditorId: Some(activeEditorId)}; + | Command("workbench.action.nextEditor") => EditorGroup.nextEditor(v) + | Command("workbench.action.previousEditor") => + EditorGroup.previousEditor(v) | ViewCloseEditor(id) => EditorGroup.removeEditorById(v, id) | ViewSetActiveEditor(id) => switch (IntMap.find_opt(id, v.editors)) { diff --git a/src/Store/CommandStoreConnector.re b/src/Store/CommandStoreConnector.re index 5f91471cad..29ab28443d 100644 --- a/src/Store/CommandStoreConnector.re +++ b/src/Store/CommandStoreConnector.re @@ -45,6 +45,18 @@ let createDefaultCommands = getState => { ~action=Command("view.closeEditor"), (), ), + Command.create( + ~category=Some("View"), + ~name="Open Next Editor", + ~action=Command("workbench.action.nextEditor"), + (), + ), + Command.create( + ~category=Some("View"), + ~name="Open Previous Editor", + ~action=Command("workbench.action.previousEditor"), + (), + ), Command.create( ~category=Some("View"), ~name="Toggle Problems (Errors, Warnings)", diff --git a/src/Store/KeyBindingsStoreConnector.re b/src/Store/KeyBindingsStoreConnector.re index c73fd6037e..4fac030af2 100644 --- a/src/Store/KeyBindingsStoreConnector.re +++ b/src/Store/KeyBindingsStoreConnector.re @@ -204,6 +204,26 @@ let start = () => { command: "view.closeEditor", condition: WhenExpr.Value(True), }, + { + key: "", + command: "workbench.action.nextEditor", + condition: WhenExpr.Value(True), + }, + { + key: "", + command: "workbench.action.nextEditor", + condition: WhenExpr.Value(True), + }, + { + key: "", + command: "workbench.action.previousEditor", + condition: WhenExpr.Value(True), + }, + { + key: "", + command: "workbench.action.previousEditor", + condition: WhenExpr.Value(True), + }, ]; let reloadConfigOnWritePost = (~configPath, dispatch) => { diff --git a/src/Store/VimStoreConnector.re b/src/Store/VimStoreConnector.re index 344bb0fef4..9171989ff8 100644 --- a/src/Store/VimStoreConnector.re +++ b/src/Store/VimStoreConnector.re @@ -904,6 +904,14 @@ let start = ) | ViewSetActiveEditor(_) => (state, synchronizeEditorEffect(state)) | ViewCloseEditor(_) => (state, synchronizeEditorEffect(state)) + | Command("workbench.action.nextEditor") => ( + state, + synchronizeEditorEffect(state), + ) + | Command("workbench.action.previousEditor") => ( + state, + synchronizeEditorEffect(state), + ) | KeyboardInput(s) => (state, inputEffect(s)) | CopyActiveFilepathToClipboard => ( state,