From 95dc0f0e199419832e5a305aeffdd93f5ea87925 Mon Sep 17 00:00:00 2001 From: Ryan Leavengood Date: Sun, 8 Mar 2020 15:31:38 -0400 Subject: [PATCH 1/3] Implement the next/previous editor commands and keybindings This wraps around like VSCode. Fixes #778 and part of #1423. --- src/Model/Actions.re | 2 ++ src/Model/EditorGroup.re | 32 ++++++++++++++++++++++++++ src/Model/EditorGroup.rei | 2 ++ src/Model/EditorGroupReducer.re | 2 ++ src/Store/CommandStoreConnector.re | 14 +++++++++++ src/Store/KeyBindingsStoreConnector.re | 20 ++++++++++++++++ src/Store/VimStoreConnector.re | 2 ++ 7 files changed, 74 insertions(+) diff --git a/src/Model/Actions.re b/src/Model/Actions.re index 6fe89a667a..4f16243154 100644 --- a/src/Model/Actions.re +++ b/src/Model/Actions.re @@ -128,6 +128,8 @@ type t = | StatusBarDisposeItem(int) | StatusBar(StatusBarModel.action) | ViewCloseEditor(int) + | ViewNextEditor + | ViewPreviousEditor | ViewSetActiveEditor(int) | EnableZenMode | DisableZenMode diff --git a/src/Model/EditorGroup.re b/src/Model/EditorGroup.re index 32005ad25a..b3c57beaa6 100644 --- a/src/Model/EditorGroup.re +++ b/src/Model/EditorGroup.re @@ -82,6 +82,38 @@ let _getAdjacentEditor = (editor: int, reverseTabOrder: list(int)) => { }; }; +let setActiveEditorByIndexDiff = (diff, model) => { + let tabs = model.reverseTabOrder; + let count = List.length(tabs); + + if (count <= 1) { + // Nothing to change + model + } else { + switch (model.activeEditorId) { + | Some(activeEditorId) => + switch (_getIndexOfElement(activeEditorId, tabs)) { + | (-1) => model + | idx => + let newIndex = + switch (idx + diff) { + // Wrapping negative, go to end + | -1 => count - 1 + // If this is past the end, go to zero, otherwise this index is fine + | i => i >= count ? 0 : i + }; + + {...model, activeEditorId: List.nth_opt(tabs, newIndex)}; + } + | None => model + }; + }; +} + +// The diff amounts are inverted because the list is in reverse order +let nextEditor = setActiveEditorByIndexDiff(-1); +let previousEditor = setActiveEditorByIndexDiff(1); + 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..792693ab29 100644 --- a/src/Model/EditorGroupReducer.re +++ b/src/Model/EditorGroupReducer.re @@ -25,6 +25,8 @@ let reduce = (v: EditorGroup.t, action: Actions.t) => { EditorGroup.getOrCreateEditorForBuffer(v, id); {...newState, activeEditorId: Some(activeEditorId)}; | ViewCloseEditor(id) => EditorGroup.removeEditorById(v, id) + | ViewNextEditor => EditorGroup.nextEditor(v) + | ViewPreviousEditor => EditorGroup.previousEditor(v) | ViewSetActiveEditor(id) => switch (IntMap.find_opt(id, v.editors)) { | None => v diff --git a/src/Store/CommandStoreConnector.re b/src/Store/CommandStoreConnector.re index 5f91471cad..a156558c62 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)", @@ -319,6 +331,8 @@ let start = (getState, contributedCommands) => { ("list.select", _ => singleActionEffect(ListSelect)), ("list.selectBackground", _ => singleActionEffect(ListSelectBackground)), ("view.closeEditor", state => closeEditorEffect(state)), + ("workbench.action.nextEditor", _ => singleActionEffect(ViewNextEditor)), + ("workbench.action.previousEditor", _ => singleActionEffect(ViewPreviousEditor)), ("view.splitVertical", state => splitEditorEffect(state, Vertical)), ("view.splitHorizontal", state => splitEditorEffect(state, Horizontal)), ( 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..c3b38b57bb 100644 --- a/src/Store/VimStoreConnector.re +++ b/src/Store/VimStoreConnector.re @@ -904,6 +904,8 @@ let start = ) | ViewSetActiveEditor(_) => (state, synchronizeEditorEffect(state)) | ViewCloseEditor(_) => (state, synchronizeEditorEffect(state)) + | ViewNextEditor => (state, synchronizeEditorEffect(state)) + | ViewPreviousEditor => (state, synchronizeEditorEffect(state)) | KeyboardInput(s) => (state, inputEffect(s)) | CopyActiveFilepathToClipboard => ( state, From dee05598bbc5a95c84e940445f9b775953ba0804 Mon Sep 17 00:00:00 2001 From: Ryan Leavengood Date: Sun, 8 Mar 2020 23:40:14 -0400 Subject: [PATCH 2/3] Remove unneeded new actions --- src/Model/Actions.re | 2 -- src/Model/EditorGroupReducer.re | 4 ++-- src/Store/CommandStoreConnector.re | 2 -- src/Store/VimStoreConnector.re | 4 ++-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Model/Actions.re b/src/Model/Actions.re index 4f16243154..6fe89a667a 100644 --- a/src/Model/Actions.re +++ b/src/Model/Actions.re @@ -128,8 +128,6 @@ type t = | StatusBarDisposeItem(int) | StatusBar(StatusBarModel.action) | ViewCloseEditor(int) - | ViewNextEditor - | ViewPreviousEditor | ViewSetActiveEditor(int) | EnableZenMode | DisableZenMode diff --git a/src/Model/EditorGroupReducer.re b/src/Model/EditorGroupReducer.re index 792693ab29..25e1005ec6 100644 --- a/src/Model/EditorGroupReducer.re +++ b/src/Model/EditorGroupReducer.re @@ -24,9 +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) - | ViewNextEditor => EditorGroup.nextEditor(v) - | ViewPreviousEditor => EditorGroup.previousEditor(v) | ViewSetActiveEditor(id) => switch (IntMap.find_opt(id, v.editors)) { | None => v diff --git a/src/Store/CommandStoreConnector.re b/src/Store/CommandStoreConnector.re index a156558c62..29ab28443d 100644 --- a/src/Store/CommandStoreConnector.re +++ b/src/Store/CommandStoreConnector.re @@ -331,8 +331,6 @@ let start = (getState, contributedCommands) => { ("list.select", _ => singleActionEffect(ListSelect)), ("list.selectBackground", _ => singleActionEffect(ListSelectBackground)), ("view.closeEditor", state => closeEditorEffect(state)), - ("workbench.action.nextEditor", _ => singleActionEffect(ViewNextEditor)), - ("workbench.action.previousEditor", _ => singleActionEffect(ViewPreviousEditor)), ("view.splitVertical", state => splitEditorEffect(state, Vertical)), ("view.splitHorizontal", state => splitEditorEffect(state, Horizontal)), ( diff --git a/src/Store/VimStoreConnector.re b/src/Store/VimStoreConnector.re index c3b38b57bb..a78c58fd09 100644 --- a/src/Store/VimStoreConnector.re +++ b/src/Store/VimStoreConnector.re @@ -904,8 +904,8 @@ let start = ) | ViewSetActiveEditor(_) => (state, synchronizeEditorEffect(state)) | ViewCloseEditor(_) => (state, synchronizeEditorEffect(state)) - | ViewNextEditor => (state, synchronizeEditorEffect(state)) - | ViewPreviousEditor => (state, synchronizeEditorEffect(state)) + | Command("workbench.action.nextEditor") => (state, synchronizeEditorEffect(state)) + | Command("workbench.action.previousEditor") => (state, synchronizeEditorEffect(state)) | KeyboardInput(s) => (state, inputEffect(s)) | CopyActiveFilepathToClipboard => ( state, From 18a29c7e33a305857d87db9939e1402894f6ef9f Mon Sep 17 00:00:00 2001 From: Ryan Leavengood Date: Mon, 9 Mar 2020 00:00:04 -0400 Subject: [PATCH 3/3] 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 | 71 ++++++++++++++++++++------------- src/Model/EditorGroupReducer.re | 3 +- src/Store/VimStoreConnector.re | 10 ++++- 3 files changed, 54 insertions(+), 30 deletions(-) diff --git a/src/Model/EditorGroup.re b/src/Model/EditorGroup.re index b3c57beaa6..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,37 +89,47 @@ let _getAdjacentEditor = (editor: int, reverseTabOrder: list(int)) => { }; }; -let setActiveEditorByIndexDiff = (diff, model) => { - let tabs = model.reverseTabOrder; - let count = List.length(tabs); - - if (count <= 1) { - // Nothing to change - model - } else { +let setActiveEditorTo = (kind, model) => + switch (model.reverseTabOrder) { + | [] + | [_] => model + | _ => switch (model.activeEditorId) { | Some(activeEditorId) => - switch (_getIndexOfElement(activeEditorId, tabs)) { - | (-1) => model - | idx => + switch (_getIndexOfElement(activeEditorId, model.reverseTabOrder)) { + | None => model + | Some(idx) => + // The diff amounts are inverted because the list is in reverse order let newIndex = - switch (idx + diff) { - // Wrapping negative, go to end - | -1 => count - 1 - // If this is past the end, go to zero, otherwise this index is fine - | i => i >= count ? 0 : i + switch (kind) { + | `Next => idx - 1 + | `Previous => idx + 1 }; - {...model, activeEditorId: List.nth_opt(tabs, newIndex)}; + 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 - }; + } }; -} -// The diff amounts are inverted because the list is in reverse order -let nextEditor = setActiveEditorByIndexDiff(-1); -let previousEditor = setActiveEditorByIndexDiff(1); +let nextEditor = setActiveEditorTo(`Next); +let previousEditor = setActiveEditorTo(`Previous); let isEmpty = model => IntMap.is_empty(model.editors); diff --git a/src/Model/EditorGroupReducer.re b/src/Model/EditorGroupReducer.re index 25e1005ec6..87cb77c786 100644 --- a/src/Model/EditorGroupReducer.re +++ b/src/Model/EditorGroupReducer.re @@ -25,7 +25,8 @@ let reduce = (v: EditorGroup.t, action: Actions.t) => { EditorGroup.getOrCreateEditorForBuffer(v, id); {...newState, activeEditorId: Some(activeEditorId)}; | Command("workbench.action.nextEditor") => EditorGroup.nextEditor(v) - | Command("workbench.action.previousEditor") => EditorGroup.previousEditor(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/VimStoreConnector.re b/src/Store/VimStoreConnector.re index a78c58fd09..9171989ff8 100644 --- a/src/Store/VimStoreConnector.re +++ b/src/Store/VimStoreConnector.re @@ -904,8 +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)) + | Command("workbench.action.nextEditor") => ( + state, + synchronizeEditorEffect(state), + ) + | Command("workbench.action.previousEditor") => ( + state, + synchronizeEditorEffect(state), + ) | KeyboardInput(s) => (state, inputEffect(s)) | CopyActiveFilepathToClipboard => ( state,