Skip to content

Commit

Permalink
Feature/command palette (onivim#138)
Browse files Browse the repository at this point in the history
* Add initial infrastructure for Command Palette

* Fix palette input sizing, fix open in command-palette module

* Add input control mode to state

add commands module and ability to close menu

* Allow for opening and closing palette

Fix multi input bug, make palette actions more versatile

* Split out palette reducer, move control mode responsibility

to the commands

* update todos

* Create Keybindings module

* Derive keybindings from json file

* Pass effects from NeovimApi to the command palette

* Add working handlers for command palette select

* Rename condition to when to match vscode syntax

* Change input control naming to match vscode

* run esy format

* Fix setup tests
  • Loading branch information
akinsho authored Mar 12, 2019
1 parent b07189f commit f17764c
Show file tree
Hide file tree
Showing 17 changed files with 321 additions and 19 deletions.
9 changes: 9 additions & 0 deletions assets/configuration/keybindings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"bindings": [
{ "key": "<C-P>", "command": "commandPalette.open", "when": ["editorTextFocus"] },
{ "key": "<ESC>", "command": "commandPalette.close", "when": ["commandPaletteFocus"] },
{ "key": "<C-N>", "command": "commandPalette.next", "when": ["commandPaletteFocus"] },
{ "key": "<C-P>", "command": "commandPalette.previous", "when": ["commandPaletteFocus"] },
{ "key": "<CR>", "command": "commandPalette.select", "when": ["commandPaletteFocus"] }
]
}
12 changes: 8 additions & 4 deletions scripts/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@ case "${machine}" in
TEXTMATE_SERVICE_PATH="$(pwd)/src/textmate_service/lib/src/index.js"
EXTENSIONS_PATH="$(pwd)/extensions"
NEOVIM_PATH="$(pwd)/vendor/neovim-0.3.3/nvim-linux64/bin/nvim"
CONFIGURATION_PATH="$(pwd)/assets/configuration/configuration.json";;
CONFIGURATION_PATH="$(pwd)/assets/configuration/configuration.json"
KEYBINDINGS_PATH="$(pwd)/assets/configuration/keybindings.json";;
Mac)
NODE_PATH="$(pwd)/vendor/node-v10.15.1/osx/node"
TEXTMATE_SERVICE_PATH="$(pwd)/src/textmate_service/lib/src/index.js"
EXTENSIONS_PATH="$(pwd)/extensions"
NEOVIM_PATH="$(pwd)/vendor/neovim-0.3.3/nvim-osx64/bin/nvim"
CONFIGURATION_PATH="$(pwd)/assets/configuration/configuration.json";;
CONFIGURATION_PATH="$(pwd)/assets/configuration/configuration.json"
KEYBINDINGS_PATH="$(pwd)/assets/configuration/keybindings.json";;
*)
NEOVIM_PATH="$(pwd)/vendor/neovim-0.3.3/nvim-win64/bin/nvim.exe"
NEOVIM_PATH="$(cygpath -m "$NEOVIM_PATH")"
Expand All @@ -54,10 +56,12 @@ case "${machine}" in
NODE_PATH="$(pwd)/vendor/node-v10.15.1/win-x64/node.exe"
NODE_PATH="$(cygpath -m "$NODE_PATH")"
CONFIGURATION_PATH="$(pwd)/assets/configuration/configuration.json"
CONFIGURATION_PATH="$(cygpath -m "$CONFIGURATION_PATH")";;
CONFIGURATION_PATH="$(cygpath -m "$CONFIGURATION_PATH")"
KEYBINDINGS_PATH="$(pwd)/assets/configuration/keybindings.json"
KEYBINDINGS_PATH="$(cygpath -m "$KEYBINDINGS_PATH")";;
esac

oni_bin_path="{neovim:\"$NEOVIM_PATH\",node:\"$NODE_PATH\",configuration:\"$CONFIGURATION_PATH\",textmateService:\"$TEXTMATE_SERVICE_PATH\",bundledExtensions:\"$EXTENSIONS_PATH\"}"
oni_bin_path="{neovim:\"$NEOVIM_PATH\",node:\"$NODE_PATH\",configuration:\"$CONFIGURATION_PATH\",textmateService:\"$TEXTMATE_SERVICE_PATH\",bundledExtensions:\"$EXTENSIONS_PATH\",keybindings:\"$KEYBINDINGS_PATH\"}"

# create the current bin path as this might not exist yet
if [ ! -d "$config_path" ]; then
Expand Down
6 changes: 6 additions & 0 deletions src/editor/Core/Actions.re
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,10 @@ type t =
| EditorMoveCursorToBottom(Cursor.move)
| SyntaxHighlightColorMap(ColorMap.t)
| SyntaxHighlightTokens(TextmateClient.TokenizationResult.t)
| CommandPaletteStart(list(Palette.command))
| CommandPaletteOpen
| CommandPaletteClose
| CommandPaletteSelect
| CommandPalettePosition(int)
| SetInputControlMode(Input.controlMode)
| Noop;
78 changes: 78 additions & 0 deletions src/editor/Core/CommandPalette.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
open Types;

type t = Palette.t;
open Palette;

let join = paths => {
let sep = Filename.dir_sep;
List.fold_left((accum, p) => accum ++ sep ++ p, "", paths);
};

let openConfigurationFile = (effects: Effects.t) => {
let path =
join([
Revery.Environment.getWorkingDirectory(),
"assets",
"configuration",
"configuration.json",
]);
effects.openFile(~path, ());
};

let openKeybindingsFile = (effects: Effects.t) => {
let path =
join([
Revery.Environment.getWorkingDirectory(),
"assets",
"configuration",
"keybindings.json",
]);
effects.openFile(~path, ());
};

let commandPaletteCommands = (effects: Effects.t) => [
{
name: "Open configuration file",
command: () => openConfigurationFile(effects),
},
{
name: "Open keybindings file",
command: () => openKeybindingsFile(effects),
},
];

let create = (~effects: option(Effects.t)=?, ()) =>
switch (effects) {
| Some(e) => {
isOpen: false,
commands: commandPaletteCommands(e),
selectedItem: 0,
}
| None => {isOpen: false, commands: [], selectedItem: 0}
};

let make = (~effects: Effects.t) =>
create(~effects, ()) |> (c => Actions.CommandPaletteStart(c.commands));

let position = (selectedItem, change, commands: list(command)) =>
selectedItem + change >= List.length(commands) ? 0 : selectedItem + change;

let reduce = (state: t, action: Actions.t) =>
switch (action) {
| CommandPaletteStart(commands) => {...state, commands}
| CommandPalettePosition(pos) => {
...state,
selectedItem: position(state.selectedItem, pos, state.commands),
}
| CommandPaletteOpen => {...state, isOpen: true}
| CommandPaletteClose => {...state, isOpen: false}
| CommandPaletteSelect =>
/**
TODO: Refactor this to middleware so this action is handled like a redux side-effect
as this makes the reducer impure
*/
let selected = List.nth(state.commands, state.selectedItem);
selected.command();
{...state, isOpen: false};
| _ => state
};
43 changes: 43 additions & 0 deletions src/editor/Core/Commands.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
type oniCommand = {
name: string,
command: unit => list(Actions.t),
};

type t = list(oniCommand);

let oniCommands = [
{
name: "commandPalette.open",
command: _ => [
CommandPaletteOpen,
SetInputControlMode(CommandPaletteFocus),
],
},
{
name: "commandPalette.close",
command: _ => [
CommandPaletteClose,
SetInputControlMode(EditorTextFocus),
],
},
{name: "commandPalette.next", command: _ => [CommandPalettePosition(1)]},
{
name: "commandPalette.previous",
command: _ => [CommandPalettePosition(-1)],
},
{
name: "commandPalette.select",
command: _ => [
CommandPaletteSelect,
SetInputControlMode(EditorTextFocus),
],
},
];

let handleCommand = (~commands=oniCommands, name) => {
let matchingCmd = List.find_opt(cmd => name == cmd.name, commands);
switch (matchingCmd) {
| Some(c) => c.command()
| None => [Noop]
};
};
1 change: 1 addition & 0 deletions src/editor/Core/Editor.re
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
open Actions;
open Types;

[@deriving show]
type t = {
id: int,
scrollX: int,
Expand Down
28 changes: 28 additions & 0 deletions src/editor/Core/Keybindings.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
open Types.Input;

[@deriving (show, yojson({strict: false, exn: false}))]
type keyBindings = {
key: string,
command: string,
[@key "when"]
condition: controlMode,
};

[@deriving (show, yojson({strict: false, exn: false}))]
type t = list(keyBindings);

[@deriving (show, yojson({strict: false, exn: false}))]
type json_keybindings = {bindings: t};

let ofFile = filePath =>
Yojson.Safe.from_file(filePath) |> json_keybindings_of_yojson;

let get = () => {
let {keybindingsPath, _}: Setup.t = Setup.init();
switch (ofFile(keybindingsPath)) {
| Ok(b) => b.bindings
| Error(e) =>
print_endline("Error parsing keybindings file ------- " ++ e);
[];
};
};
3 changes: 3 additions & 0 deletions src/editor/Core/Oni_Core.re
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ module Types = Types;
module Utility = Utility;
module Wildmenu = Wildmenu;
module Configuration = Configuration;
module CommandPalette = CommandPalette;
module Commands = Commands;
module Keybindings = Keybindings;
2 changes: 2 additions & 0 deletions src/editor/Core/Reducer.re
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ let reduce: (State.t, Actions.t) => State.t =
syntaxHighlighting: SyntaxHighlighting.reduce(s.syntaxHighlighting, a),
wildmenu: Wildmenu.reduce(s.wildmenu, a),
commandline: Commandline.reduce(s.commandline, a),
commandPalette: CommandPalette.reduce(s.commandPalette, a),
};

switch (a) {
Expand Down Expand Up @@ -103,6 +104,7 @@ let reduce: (State.t, Actions.t) => State.t =
...s,
tabs: updateTabs(activeBufferId, modified, s.tabs),
}
| SetInputControlMode(m) => {...s, inputControlMode: m}
| _ => s
};
};
2 changes: 2 additions & 0 deletions src/editor/Core/Setup.re
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ type t = {
bundledExtensionsPath: string,
[@key "configuration"]
configPath: string,
[@key "keybindings"]
keybindingsPath: string,
};

let ofString = str => Yojson.Safe.from_string(str) |> of_yojson_exn;
Expand Down
4 changes: 4 additions & 0 deletions src/editor/Core/State.re
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,21 @@ type t = {
buffers: BufferMap.t,
activeBufferId: int,
editorFont: EditorFont.t,
commandPalette: CommandPalette.t,
commandline: Commandline.t,
wildmenu: Wildmenu.t,
configuration: Configuration.t,
syntaxHighlighting: SyntaxHighlighting.t,
theme: Theme.t,
editor: Editor.t,
inputControlMode: Input.controlMode,
};

let create: unit => t =
() => {
configuration: Configuration.create(),
mode: Insert,
commandPalette: CommandPalette.create(),
commandline: Commandline.create(),
wildmenu: Wildmenu.create(),
activeBufferId: 0,
Expand All @@ -51,4 +54,5 @@ let create: unit => t =
tabs: [Tab.create(0, "[No Name]")],
theme: Theme.create(),
editor: Editor.create(),
inputControlMode: EditorTextFocus,
};
28 changes: 28 additions & 0 deletions src/editor/Core/Types.re
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module Index = {
};

module EditorSize = {
[@deriving show]
type t = {
pixelWidth: int,
pixelHeight: int,
Expand Down Expand Up @@ -76,6 +77,7 @@ type openMethod =
| Buffer;

module BufferPosition = {
[@deriving show]
type t = {
line: Index.t,
character: Index.t,
Expand Down Expand Up @@ -223,3 +225,29 @@ type commandline = {
prompt: string,
show: bool,
};

module Palette = {
[@deriving show]
type command = {
name: string,
command: unit => unit,
};

[@deriving show]
type t = {
isOpen: bool,
commands: list(command),
selectedItem: int,
};
};

module Input = {
[@deriving (show, yojson({strict: false, exn: false}))]
type controlMode =
| [@name "commandPaletteFocus"] CommandPaletteFocus
| [@name "editorTextFocus"] EditorTextFocus;
};

module Effects = {
type t = {openFile: Views.viewOperation};
};
52 changes: 52 additions & 0 deletions src/editor/UI/CommandPaletteView.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
open Revery;
open Oni_Core;
open Revery.UI;
open Revery.UI.Components;

let component = React.component("commandPalette");

let paletteWidth = 400;

let containerStyles = (theme: Theme.t) =>
Style.[
backgroundColor(theme.colors.editorMenuBackground),
color(theme.colors.editorMenuForeground),
width(paletteWidth),
height(300),
boxShadow(
~xOffset=-15.,
~yOffset=5.,
~blurRadius=30.,
~spreadRadius=5.,
~color=Color.rgba(0., 0., 0., 0.2),
),
];

let paletteItemStyle = Style.[fontSize(14)];

let createElement =
(~children as _, ~commandPalette: CommandPalette.t, ~theme: Theme.t, ()) =>
component(hooks =>
(
hooks,
commandPalette.isOpen
? <View style={containerStyles(theme)}>
/* <Input style=Style.[width(paletteWidth)] /> */

<ScrollView style=Style.[height(350)]>
...{List.mapi(
(index, cmd: Types.Palette.command) =>
<MenuItem
icon=""
style=paletteItemStyle
label={cmd.name}
selected={index == commandPalette.selectedItem}
theme
/>,
commandPalette.commands,
)}
</ScrollView>
</View>
: React.listToElement([]),
)
);
1 change: 1 addition & 0 deletions src/editor/UI/Root.re
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ let createElement = (~state: State.t, ~children as _, ()) =>
<Overlay>
<CommandlineView theme command={state.commandline} />
<WildmenuView theme wildmenu={state.wildmenu} />
<CommandPaletteView theme commandPalette={state.commandPalette} />
</Overlay>
<View style=statusBarStyle>
<StatusBar
Expand Down
Loading

0 comments on commit f17764c

Please sign in to comment.