diff --git a/src/Core/Utility.re b/src/Core/Utility.re
index 358b7db620..8fb4157bd6 100644
--- a/src/Core/Utility.re
+++ b/src/Core/Utility.re
@@ -254,6 +254,24 @@ module RangeUtil = {
};
};
+module ArrayEx = {
+ exception Found(int);
+
+ let findIndex = (predicate, array) =>
+ try(
+ {
+ for (i in 0 to Array.length(array) - 1) {
+ if (predicate(array[i])) {
+ raise(Found(i));
+ };
+ };
+ None;
+ }
+ ) {
+ | Found(i) => Some(i)
+ };
+};
+
// TODO: Remove after 4.08 upgrade
module List = {
include List;
@@ -683,3 +701,15 @@ module ChunkyQueue: {
let toList = ({front, rear, _}) =>
front @ (Queue.toList(rear) |> List.concat);
};
+
+module Path = {
+ // Not very robust path-handling utilities.
+ // TODO: Make good
+
+ let toRelative = (~base, path) => {
+ let base = base == "/" ? base : base ++ Filename.dir_sep;
+ Str.replace_first(Str.regexp_string(base), "", path);
+ };
+
+ let explode = String.split_on_char(Filename.dir_sep.[0]);
+};
diff --git a/src/Model/FileExplorer.re b/src/Model/FileExplorer.re
index a7660ac957..e5d2d90720 100644
--- a/src/Model/FileExplorer.re
+++ b/src/Model/FileExplorer.re
@@ -1,4 +1,3 @@
-open Revery;
open Oni_Core;
open Oni_Extensions;
@@ -6,19 +5,20 @@ type t = {
tree: option(FsTreeNode.t),
isOpen: bool,
scrollOffset: [ | `Start(float) | `Middle(float)],
- focus: option(string) // path
+ active: option(string), // path
+ focus: option(string) // node id
};
[@deriving show({with_path: false})]
type action =
| TreeLoaded([@opaque] FsTreeNode.t)
- | NodeLoaded(int, [@opaque] FsTreeNode.t)
- | FocusNodeLoaded(int, [@opaque] FsTreeNode.t)
+ | NodeLoaded(string, [@opaque] FsTreeNode.t)
+ | FocusNodeLoaded(string, [@opaque] FsTreeNode.t)
| NodeClicked([@opaque] FsTreeNode.t)
- | ScrollOffsetChanged([ | `Start(float) | `Middle(float)]);
-
-module ExplorerId =
- UniqueId.Make({});
+ | ScrollOffsetChanged([ | `Start(float) | `Middle(float)])
+ | Select
+ | FocusPrev
+ | FocusNext;
let getFileIcon = (languageInfo, iconTheme, filePath) => {
let fileIcon =
@@ -79,7 +79,6 @@ let getFilesAndFolders = (~ignored, cwd, getIcon) => {
let rec getDirContent = (~loadChildren=false, cwd) => {
let toFsTreeNode = file => {
let path = Filename.concat(cwd, file);
- let id = ExplorerId.getUniqueId();
if (isDirectory(path)) {
let%lwt children =
@@ -94,10 +93,11 @@ let getFilesAndFolders = (~ignored, cwd, getIcon) => {
Lwt.return([]);
};
- FsTreeNode.directory(path, ~id, ~icon=getIcon(path), ~children)
- |> Lwt.return;
+ Lwt.return(
+ FsTreeNode.directory(path, ~icon=getIcon(path), ~children),
+ );
} else {
- FsTreeNode.file(path, ~id, ~icon=getIcon(path)) |> Lwt.return;
+ FsTreeNode.file(path, ~icon=getIcon(path)) |> Lwt.return;
};
};
@@ -116,25 +116,19 @@ let getFilesAndFolders = (~ignored, cwd, getIcon) => {
};
let getDirectoryTree = (cwd, languageInfo, iconTheme, ignored) => {
- let id = ExplorerId.getUniqueId();
let getIcon = getFileIcon(languageInfo, iconTheme);
let children =
getFilesAndFolders(~ignored, cwd, getIcon)
|> Lwt_main.run
|> List.sort(sortByLoweredDisplayName);
- FsTreeNode.directory(
- cwd,
- ~id,
- ~icon=getIcon(cwd),
- ~children,
- ~isOpen=true,
- );
+ FsTreeNode.directory(cwd, ~icon=getIcon(cwd), ~children, ~isOpen=true);
};
let initial = {
tree: None,
isOpen: true,
scrollOffset: `Start(0.),
+ active: None,
focus: None,
};
diff --git a/src/Model/FileExplorer.rei b/src/Model/FileExplorer.rei
index 7ccdf2f35c..6c42bddf09 100644
--- a/src/Model/FileExplorer.rei
+++ b/src/Model/FileExplorer.rei
@@ -4,16 +4,20 @@ type t = {
tree: option(FsTreeNode.t),
isOpen: bool,
scrollOffset: [ | `Start(float) | `Middle(float)],
- focus: option(string) // path
+ active: option(string), // path
+ focus: option(string) // node id
};
[@deriving show]
type action =
| TreeLoaded([@opaque] FsTreeNode.t)
- | NodeLoaded(int, [@opaque] FsTreeNode.t)
- | FocusNodeLoaded(int, [@opaque] FsTreeNode.t)
+ | NodeLoaded(string, [@opaque] FsTreeNode.t)
+ | FocusNodeLoaded(string, [@opaque] FsTreeNode.t)
| NodeClicked([@opaque] FsTreeNode.t)
- | ScrollOffsetChanged([ | `Start(float) | `Middle(float)]);
+ | ScrollOffsetChanged([ | `Start(float) | `Middle(float)])
+ | Select
+ | FocusPrev
+ | FocusNext;
let initial: t;
diff --git a/src/Model/FsTreeNode.re b/src/Model/FsTreeNode.re
index 3810f41b4e..b90f2f22f8 100644
--- a/src/Model/FsTreeNode.re
+++ b/src/Model/FsTreeNode.re
@@ -1,5 +1,9 @@
+open Oni_Core;
+
+module ArrayEx = Utility.ArrayEx;
+module Path = Utility.Path;
+
type t = {
- id: int,
path: string,
displayName: string,
hash: int, // hash of basename, so only comparable locally
@@ -15,6 +19,10 @@ and kind =
})
| File;
+let _hash = Hashtbl.hash;
+let _pathHashes = (~base, path) =>
+ path |> Path.toRelative(~base) |> Path.explode |> List.map(_hash);
+
let rec countExpandedSubtree =
fun
| Directory({isOpen: true, children}) =>
@@ -26,49 +34,48 @@ let rec countExpandedSubtree =
| _ => 1;
-let file = (path, ~id, ~icon) => {
+let file = (path, ~icon) => {
let basename = Filename.basename(path);
{
- id,
path,
+ hash: _hash(basename),
displayName: basename,
- hash: Hashtbl.hash(basename),
icon,
kind: File,
expandedSubtreeSize: 1,
};
};
-let directory = (~isOpen=false, path, ~id, ~icon, ~children) => {
+let directory = (~isOpen=false, path, ~icon, ~children) => {
let kind = Directory({isOpen, children});
let basename = Filename.basename(path);
{
- id,
path,
+ hash: _hash(basename),
displayName: basename,
- hash: Hashtbl.hash(basename),
icon,
kind,
expandedSubtreeSize: countExpandedSubtree(kind),
};
};
-let findNodesByLocalPath = (path, tree) => {
- let pathHashes =
- path
- |> String.split_on_char(Filename.dir_sep.[0])
- |> List.map(Hashtbl.hash);
+let equals = (a, b) => a.hash == b.hash && a.path == b.path;
- let rec loop = (focusedNodes, children, pathSegments) =>
- switch (pathSegments) {
- | [] => `Success(focusedNodes |> List.rev)
+let findNodesByPath = (path, tree) => {
+ let rec loop = (focusedNodes, children, pathHashes) =>
+ switch (pathHashes) {
+ | [] => `Success(List.rev(focusedNodes))
| [hash, ...rest] =>
switch (children) {
| [] =>
- let last = focusedNodes |> List.hd;
- last.id == tree.id ? `Failed : `Partial(last);
+ let last = List.hd(focusedNodes);
+ if (equals(last, tree)) {
+ `Failed;
+ } else {
+ `Partial(last);
+ };
| [node, ...children] =>
if (node.hash == hash) {
@@ -79,21 +86,162 @@ let findNodesByLocalPath = (path, tree) => {
};
loop([node, ...focusedNodes], children, rest);
} else {
- loop(focusedNodes, children, pathSegments);
+ loop(focusedNodes, children, pathHashes);
}
}
};
switch (tree.kind) {
- | Directory({children, _}) => loop([tree], children, pathHashes)
+ | Directory({children, _}) =>
+ loop([tree], children, _pathHashes(~base=tree.path, path))
| File => `Failed
};
};
-let update = (~tree, ~updater, nodeId) => {
+let findByPath = (path, tree) => {
+ let rec loop = (node, children, pathHashes) =>
+ switch (pathHashes) {
+ | [] => Some(node)
+ | [hash, ...rest] =>
+ switch (children) {
+ | [] => None
+
+ | [node, ...children] =>
+ if (node.hash == hash) {
+ let children =
+ switch (node.kind) {
+ | Directory({children, _}) => children
+ | File => []
+ };
+ loop(node, children, rest);
+ } else {
+ loop(node, children, pathHashes);
+ }
+ }
+ };
+
+ switch (tree.kind) {
+ | Directory({children, _}) =>
+ loop(tree, children, _pathHashes(~base=tree.path, path))
+ | File => None
+ };
+};
+
+let prevExpandedNode = (path, tree) =>
+ switch (findNodesByPath(path, tree)) {
+ | `Success(nodePath) =>
+ switch (List.rev(nodePath)) {
+ // Has a parent, and therefore also siblings
+ | [focus, {kind: Directory({children, _}), _} as parent, ..._] =>
+ let children = children |> Array.of_list;
+ let index = children |> ArrayEx.findIndex(equals(focus));
+
+ switch (index) {
+ // is first child
+ | Some(index) when index == 0 => Some(parent)
+
+ | Some(index) =>
+ switch (children[index - 1]) {
+ // is open directory with at least one child
+ | {
+ kind: Directory({isOpen: true, children: [_, ..._] as children}),
+ _,
+ } =>
+ let children = children |> Array.of_list;
+ let lastChild = children[Array.length(children) - 1];
+ Some(lastChild);
+
+ | prev => Some(prev)
+ }
+
+ | None => None // is not a child of its parent (?!)
+ };
+ | _ => None // has neither parent or siblings
+ }
+ | `Partial(_)
+ | `Failed => None // path does not exist in this ree
+ };
+
+let nextExpandedNode = (path, tree) =>
+ switch (findNodesByPath(path, tree)) {
+ | `Success(nodePath) =>
+ let rec loop = revNodePath =>
+ switch (revNodePath) {
+ | [
+ focus,
+ ...[{kind: Directory({children, _}), _}, ..._] as ancestors,
+ ] =>
+ let children = children |> Array.of_list;
+ let index = children |> ArrayEx.findIndex(equals(focus));
+
+ switch (index) {
+ // is last child
+ | Some(index) when index == Array.length(children) - 1 =>
+ loop(ancestors)
+
+ | Some(index) =>
+ let next = children[index + 1];
+ Some(next);
+
+ | None => None // is not a child of its parent (?!)
+ };
+ | _ => None // has neither parent or siblings
+ };
+
+ switch (List.rev(nodePath)) {
+ // Focus is open directory with at least one child
+ | [
+ {kind: Directory({isOpen: true, children: [firstChild, ..._]}), _},
+ ..._,
+ ] =>
+ Some(firstChild)
+ | revNodePath => loop(revNodePath)
+ };
+
+ | `Partial(_)
+ | `Failed => None // path does not exist in this tree
+ };
+
+// Counts the number of expanded nodes before the node specified by the given path
+let expandedIndex = (tree, path) => {
+ let rec loop = (node, path) =>
+ switch (path) {
+ | [] => `Found(0)
+ | [focus, ...focusTail] =>
+ if (equals(focus, node)) {
+ `NotFound(node.expandedSubtreeSize);
+ } else {
+ switch (node.kind) {
+ | Directory({isOpen: false, _})
+ | File => `Found(0)
+
+ | Directory({isOpen: true, children}) =>
+ let rec loopChildren = (count, children) =>
+ switch (children) {
+ | [] => `NotFound(count)
+ | [child, ...childTail] =>
+ switch (loop(child, focusTail)) {
+ | `Found(subtreeCount) => `Found(count + subtreeCount)
+ | `NotFound(subtreeCount) =>
+ loopChildren(count + subtreeCount, childTail)
+ }
+ };
+
+ loopChildren(1, children);
+ };
+ }
+ };
+
+ switch (loop(tree, path)) {
+ | `Found(count) => Some(count)
+ | `NotFound(_) => None
+ };
+};
+
+let update = (~tree, ~updater, targetPath) => {
let rec loop =
fun
- | {id, _} as node when id == nodeId => updater(node)
+ | {path, _} as node when path == targetPath => updater(node)
| {kind: Directory({children, _} as dir), _} as node => {
let kind = Directory({...dir, children: List.map(loop, children)});
@@ -108,7 +256,7 @@ let update = (~tree, ~updater, nodeId) => {
let updateNodesInPath = (~tree, ~updater, nodes) => {
let rec loop = (nodes, node) =>
switch (nodes) {
- | [{id, kind, _}, ...rest] when id == node.id =>
+ | [{hash, kind, _}, ...rest] when hash == node.hash =>
switch (kind) {
| Directory({children, _} as dir) =>
let newChildren = List.map(loop(rest), children);
diff --git a/src/Model/FsTreeNode.rei b/src/Model/FsTreeNode.rei
index 24fb589b52..fc09a614b2 100644
--- a/src/Model/FsTreeNode.rei
+++ b/src/Model/FsTreeNode.rei
@@ -1,6 +1,5 @@
type t =
pri {
- id: int,
path: string,
displayName: string,
hash: int, // hash of basename, so only comparable locally
@@ -17,25 +16,32 @@ and kind =
})
| File;
-let file: (string, ~id: int, ~icon: option(IconTheme.IconDefinition.t)) => t;
+let file: (string, ~icon: option(IconTheme.IconDefinition.t)) => t;
let directory:
(
~isOpen: bool=?,
string,
- ~id: int,
~icon: option(IconTheme.IconDefinition.t),
~children: list(t)
) =>
t;
-let findNodesByLocalPath:
+let findNodesByPath:
(string, t) => [ | `Success(list(t)) | `Partial(t) | `Failed];
+let findByPath: (string, t) => option(t);
-let update: (~tree: t, ~updater: t => t, int) => t;
+let prevExpandedNode: (string, t) => option(t);
+let nextExpandedNode: (string, t) => option(t);
+
+let expandedIndex: (t, list(t)) => option(int);
+
+let update: (~tree: t, ~updater: t => t, string) => t;
let updateNodesInPath: (~tree: t, ~updater: t => t, list(t)) => t;
let toggleOpen: t => t;
let setOpen: t => t;
+let equals: (t, t) => bool;
+
module Model: {
type nonrec t = t;
diff --git a/src/Model/Workspace.re b/src/Model/Workspace.re
index aab3c53e2f..0235e3efd5 100644
--- a/src/Model/Workspace.re
+++ b/src/Model/Workspace.re
@@ -16,8 +16,3 @@ type workspace = {
type t = option(workspace);
let initial: t = None;
-
-let toRelativePath = (base, path) => {
- let base = base == "/" ? base : base ++ Filename.dir_sep;
- Str.replace_first(Str.regexp_string(base), "", path);
-};
diff --git a/src/Store/FileExplorerStore.re b/src/Store/FileExplorerStore.re
index c277426fbe..2142151e33 100644
--- a/src/Store/FileExplorerStore.re
+++ b/src/Store/FileExplorerStore.re
@@ -26,58 +26,23 @@ module Effects = {
};
};
-// Counts the number of axpanded nodes before the node specified by the given path
-let nodeOffsetByPath = (tree, path) => {
- let rec loop = (node: FsTreeNode.t, path) =>
- switch (path) {
- | [] => `Found(0)
- | [(focus: FsTreeNode.t), ...focusTail] =>
- if (focus.id != node.id) {
- `NotFound(node.expandedSubtreeSize);
- } else {
- switch (node.kind) {
- | Directory({isOpen: false, _})
- | File => `Found(0)
-
- | Directory({isOpen: true, children}) =>
- let rec loopChildren = (count, children) =>
- switch (children) {
- | [] => `NotFound(count)
- | [child, ...childTail] =>
- switch (loop(child, focusTail)) {
- | `Found(subtreeCount) => `Found(count + subtreeCount)
- | `NotFound(subtreeCount) =>
- loopChildren(count + subtreeCount, childTail)
- }
- };
- loopChildren(1, children);
- };
- }
- };
-
- switch (loop(tree, path)) {
- | `Found(count) => Some(count)
- | `NotFound(_) => None
- };
-};
-
let updateFileExplorer = (updater, state) =>
State.{...state, fileExplorer: updater(state.fileExplorer)};
let setTree = (tree, state) =>
updateFileExplorer(s => {...s, tree: Some(tree)}, state);
let setOpen = (isOpen, state) =>
updateFileExplorer(s => {...s, isOpen}, state);
-let setFocus = (focus, state) =>
- updateFileExplorer(s => {...s, focus}, state);
+let setActive = (maybePath, state) =>
+ updateFileExplorer(s => {...s, active: maybePath}, state);
+let setFocus = (maybePath, state) =>
+ updateFileExplorer(s => {...s, focus: maybePath}, state);
let setScrollOffset = (scrollOffset, state) =>
updateFileExplorer(s => {...s, scrollOffset}, state);
let revealPath = (state: State.t, path) => {
- switch (state.fileExplorer.tree, state.workspace) {
- | (Some(tree), Some({workingDirectory, _})) =>
- let localPath = Workspace.toRelativePath(workingDirectory, path);
-
- switch (FsTreeNode.findNodesByLocalPath(localPath, tree)) {
+ switch (state.fileExplorer.tree) {
+ | Some(tree) =>
+ switch (FsTreeNode.findNodesByPath(path, tree)) {
// Nothing to do
| `Success([])
| `Failed => (state, Isolinear.Effect.none)
@@ -91,7 +56,7 @@ let revealPath = (state: State.t, path) => {
state.iconTheme,
state.configuration,
~onComplete=node =>
- Actions.FileExplorer(FocusNodeLoaded(lastNode.id, node))
+ Actions.FileExplorer(FocusNodeLoaded(lastNode.path, node))
),
)
@@ -104,7 +69,7 @@ let revealPath = (state: State.t, path) => {
~updater=FsTreeNode.setOpen,
);
let offset =
- switch (nodeOffsetByPath(tree, nodes)) {
+ switch (FsTreeNode.expandedIndex(tree, nodes)) {
| Some(offset) => `Middle(float(offset))
| None => state.fileExplorer.scrollOffset
};
@@ -113,9 +78,9 @@ let revealPath = (state: State.t, path) => {
state |> setTree(tree) |> setScrollOffset(offset),
Isolinear.Effect.none,
);
- };
+ }
- | _ => (state, Isolinear.Effect.none)
+ | None => (state, Isolinear.Effect.none)
};
};
@@ -130,51 +95,97 @@ let start = () => {
});
};
- let updater = (state: State.t, action: FileExplorer.action) => {
- let replaceNode = (nodeId, node) =>
- switch (state.fileExplorer.tree) {
- | Some(tree) =>
- setTree(FsTreeNode.update(nodeId, ~tree, ~updater=_ => node), state)
- | None => state
- };
+ let replaceNode = (path, node, state: State.t) =>
+ switch (state.fileExplorer.tree) {
+ | Some(tree) =>
+ setTree(FsTreeNode.update(path, ~tree, ~updater=_ => node), state)
+ | None => state
+ };
+
+ let selectNode = (node: FsTreeNode.t, state) =>
+ switch (node) {
+ | {kind: File, path, _} =>
+ // Set active here to avoid scrolling in BufferEnter
+ (state |> setActive(Some(node.path)), openFileByPathEffect(path))
+
+ | {kind: Directory({isOpen, _}), _} => (
+ replaceNode(node.path, FsTreeNode.toggleOpen(node), state),
+ isOpen
+ ? Isolinear.Effect.none
+ : Effects.load(
+ node.path,
+ state.languageInfo,
+ state.iconTheme,
+ state.configuration,
+ ~onComplete=newNode =>
+ Actions.FileExplorer(NodeLoaded(node.path, newNode))
+ ),
+ )
+ };
+ let updater = (state: State.t, action: FileExplorer.action) => {
switch (action) {
| TreeLoaded(tree) => (setTree(tree, state), Isolinear.Effect.none)
- | NodeLoaded(id, node) => (replaceNode(id, node), Isolinear.Effect.none)
+ | NodeLoaded(path, node) => (
+ replaceNode(path, node, state),
+ Isolinear.Effect.none,
+ )
- | FocusNodeLoaded(id, node) =>
- switch (state.fileExplorer.focus) {
- | Some(path) => revealPath(replaceNode(id, node), path)
+ | FocusNodeLoaded(path, node) =>
+ switch (state.fileExplorer.active) {
+ | Some(activePath) =>
+ let state = replaceNode(path, node, state);
+ revealPath(state, activePath);
| None => (state, Isolinear.Effect.none)
}
| NodeClicked(node) =>
- // Set focus here to avoid scrolling in BufferEnter
- let state = setFocus(Some(node.path), state);
-
- switch (node) {
- | {kind: File, path, _} => (state, openFileByPathEffect(path))
-
- | {kind: Directory({isOpen, _}), _} => (
- replaceNode(node.id, FsTreeNode.toggleOpen(node)),
- isOpen
- ? Isolinear.Effect.none
- : Effects.load(
- node.path,
- state.languageInfo,
- state.iconTheme,
- state.configuration,
- ~onComplete=newNode =>
- Actions.FileExplorer(NodeLoaded(node.id, newNode))
- ),
- )
- };
+ let state = state |> setFocus(Some(node.path));
+ selectNode(node, state);
| ScrollOffsetChanged(offset) => (
setScrollOffset(offset, state),
Isolinear.Effect.none,
)
+
+ | Select =>
+ switch (state.fileExplorer.tree, state.fileExplorer.focus) {
+ | (Some(tree), Some(path)) =>
+ switch (FsTreeNode.findByPath(path, tree)) {
+ | Some(node) => selectNode(node, state)
+ | None => (state, Isolinear.Effect.none)
+ }
+ | _ => (state, Isolinear.Effect.none)
+ }
+
+ | FocusPrev =>
+ switch (state.fileExplorer.tree, state.fileExplorer.focus) {
+ | (Some(tree), Some(path)) =>
+ switch (FsTreeNode.prevExpandedNode(path, tree)) {
+ | Some(node) => (
+ setFocus(Some(node.path), state),
+ Isolinear.Effect.none,
+ )
+ | None => (state, Isolinear.Effect.none)
+ }
+
+ | _ => (state, Isolinear.Effect.none)
+ }
+
+ | FocusNext =>
+ switch (state.fileExplorer.tree, state.fileExplorer.focus) {
+ | (Some(tree), Some(path)) =>
+ switch (FsTreeNode.nextExpandedNode(path, tree)) {
+ | Some(node) => (
+ setFocus(Some(node.path), state),
+ Isolinear.Effect.none,
+ )
+ | None => (state, Isolinear.Effect.none)
+ }
+
+ | _ => (state, Isolinear.Effect.none)
+ }
};
};
@@ -212,8 +223,8 @@ let start = () => {
| BufferEnter({filePath, _}, _) =>
switch (state.fileExplorer) {
- | {focus, _} when focus != filePath =>
- let state = setFocus(filePath, state);
+ | {active, _} when active != filePath =>
+ let state = setActive(filePath, state);
switch (filePath) {
| Some(path) => revealPath(state, path)
| None => (state, Isolinear.Effect.none)
diff --git a/src/Store/QuickmenuStoreConnector.re b/src/Store/QuickmenuStoreConnector.re
index 6619fc5d30..5a285a0ac5 100644
--- a/src/Store/QuickmenuStoreConnector.re
+++ b/src/Store/QuickmenuStoreConnector.re
@@ -12,6 +12,7 @@ module Actions = Model.Actions;
module Animation = Model.Animation;
module Quickmenu = Model.Quickmenu;
module Utility = Core.Utility;
+module Path = Utility.Path;
module ExtensionContributions = Oni_Extensions.ExtensionContributions;
module Log = (val Core.Log.withNamespace("Oni2.QuickmenuStore"));
@@ -69,7 +70,7 @@ let start = (themeInfo: Model.ThemeInfo.t) => {
Some(
Actions.{
category: None,
- name: Model.Workspace.toRelativePath(currentDirectory, path),
+ name: Path.toRelative(~base=currentDirectory, path),
command: () => {
Model.Actions.OpenFileByPath(path, None, None);
},
@@ -375,7 +376,7 @@ let subscriptions = ripgrep => {
let stringToCommand = (languageInfo, iconTheme, fullPath) =>
Actions.{
category: None,
- name: Model.Workspace.toRelativePath(directory, fullPath),
+ name: Path.toRelative(~base=directory, fullPath),
command: () => Model.Actions.OpenFileByPath(fullPath, None, None),
icon:
Model.FileExplorer.getFileIcon(languageInfo, iconTheme, fullPath),
diff --git a/src/UI/FileExplorerView.re b/src/UI/FileExplorerView.re
index 0abe2bffc9..e569f80260 100644
--- a/src/UI/FileExplorerView.re
+++ b/src/UI/FileExplorerView.re
@@ -11,7 +11,7 @@ let make = (~state: State.t, ()) => {
switch (state.fileExplorer) {
| {tree: None, _} => React.empty
- | {tree: Some(tree), focus, _} =>
-
+ | {tree: Some(tree), active, focus, _} =>
+
};
};
diff --git a/src/UI/FileTreeView.re b/src/UI/FileTreeView.re
index 07985d45aa..ea298232e4 100644
--- a/src/UI/FileTreeView.re
+++ b/src/UI/FileTreeView.re
@@ -1,33 +1,44 @@
+open Oni_Core;
open Oni_Model;
open Revery;
open Revery.UI;
-module Core = Oni_Core;
-
-let toNodePath = (workspace: Workspace.workspace, tree, path) =>
- Core.Log.perf("FileTreeview.toNodePath", () => {
- let localPath =
- Workspace.toRelativePath(workspace.workingDirectory, path);
-
- switch (FsTreeNode.findNodesByLocalPath(localPath, tree)) {
- | `Success(nodes) => Some(nodes)
- | `Partial(_)
- | `Failed => None
- };
- });
+module Option = Utility.Option;
module Styles = {
open Style;
let container = [flexGrow(1)];
- let item = [flexDirection(`Row), alignItems(`Center)];
-
- let text = (~fg, ~font: Core.UiFont.t) => [
+ let title = (~fg, ~bg, ~font: UiFont.t) => [
fontSize(font.fontSize),
fontFamily(font.fontFile),
+ backgroundColor(bg),
color(fg),
+ ];
+
+ let heading = (theme: Theme.t) => [
+ flexDirection(`Row),
+ justifyContent(`Center),
+ alignItems(`Center),
+ backgroundColor(theme.sideBarBackground),
+ height(Constants.default.tabHeight),
+ ];
+
+ let item = (~isFocus, ~theme: Theme.t) => [
+ flexDirection(`Row),
+ flexGrow(1),
+ alignItems(`Center),
+ backgroundColor(
+ isFocus ? theme.menuSelectionBackground : theme.sideBarBackground,
+ ),
+ ];
+
+ let text = (~isActive, ~theme: Theme.t, ~font: UiFont.t) => [
+ fontSize(font.fontSize),
+ fontFamily(font.fontFile),
+ color(isActive ? theme.oniNormalModeBackground : theme.sideBarForeground),
marginLeft(10),
marginVertical(2),
textWrap(TextWrapping.NoWrap),
@@ -49,7 +60,15 @@ let setiIcon = (~icon, ~fontSize as size, ~fg, ()) => {
/>;
};
-let nodeView = (~font: Core.UiFont.t, ~fg, ~node: FsTreeNode.t, ()) => {
+let nodeView =
+ (
+ ~isFocus,
+ ~isActive,
+ ~font: UiFont.t,
+ ~theme: Theme.t,
+ ~node: FsTreeNode.t,
+ (),
+ ) => {
let icon = () =>
switch (node.icon) {
| Some(icon) =>
@@ -67,56 +86,80 @@ let nodeView = (~font: Core.UiFont.t, ~fg, ~node: FsTreeNode.t, ()) => {
switch (node.kind) {
| Directory({isOpen, _}) =>
| _ =>
};
-
+
-
+
;
};
module TreeView = TreeView.Make(FsTreeNode.Model);
-let make =
- (
- ~tree: FsTreeNode.t,
- ~focus: option(string),
- ~onNodeClick,
- ~state: State.t,
- (),
- ) => {
+let%component make =
+ (
+ ~tree: FsTreeNode.t,
+ ~active: option(string),
+ ~focus: option(string),
+ ~onNodeClick,
+ ~state: State.t,
+ (),
+ ) => {
+ let%hook (containerRef, setContainerRef) = Hooks.ref(None);
+
[@warning "-27"]
let State.{theme, uiFont as font, _} = state;
- let fg = state.theme.sideBarForeground;
-
- let focus =
- switch (focus, state.workspace) {
- | (Some(path), Some(workspace)) => toNodePath(workspace, tree, path)
- | _ => None
- };
-
let FileExplorer.{scrollOffset, _} = state.fileExplorer;
let onScrollOffsetChange = offset =>
GlobalContext.current().dispatch(
FileExplorer(ScrollOffsetChanged(offset)),
);
-
+ let onNodeClick = node => {
+ Option.iter(Revery.UI.Focus.focus, containerRef);
+ onNodeClick(node);
+ };
+
+ let onKeyDown = (event: NodeEvents.keyEventParams) => {
+ switch (event.keycode) {
+ // Enter
+ | v when v == 13 =>
+ GlobalContext.current().dispatch(Actions.FileExplorer(Select))
+
+ // arrow up
+ | v when v == 1073741906 =>
+ GlobalContext.current().dispatch(Actions.FileExplorer(FocusPrev))
+
+ // arrow down
+ | v when v == 1073741905 =>
+ GlobalContext.current().dispatch(Actions.FileExplorer(FocusNext))
+
+ | _ => ()
+ };
+ };
+
+ setContainerRef(Some(ref))}>
- ...{node => }
+ scrollOffset onScrollOffsetChange tree itemHeight=22 onClick=onNodeClick>
+ ...{node =>
+
+ }
;
};
diff --git a/src/UI/LocationListView.re b/src/UI/LocationListView.re
index 634883fdb9..fab48974e5 100644
--- a/src/UI/LocationListView.re
+++ b/src/UI/LocationListView.re
@@ -6,6 +6,7 @@ open Oni_Core;
open Oni_Model;
module Option = Utility.Option;
+module Path = Utility.Path;
// TODO: move to Revery
let getFontAdvance = (fontFile, fontSize) => {
@@ -84,7 +85,7 @@ let item =
let locationText =
Printf.sprintf(
"%s:%n - ",
- Workspace.toRelativePath(workingDirectory, item.file),
+ Path.toRelative(~base=workingDirectory, item.file),
Index.toOneBased(item.location.line),
);
diff --git a/src/UI/TreeView.re b/src/UI/TreeView.re
index 208e75fe5e..cd5e9f6a49 100644
--- a/src/UI/TreeView.re
+++ b/src/UI/TreeView.re
@@ -53,14 +53,11 @@ module Styles = {
bottom(0),
];
- let item = (~itemHeight, ~isFocused, ~theme: Theme.t) => [
+ let item = (~itemHeight) => [
height(itemHeight),
cursor(Revery.MouseCursors.pointer),
flexDirection(`Row),
overflow(`Hidden),
- backgroundColor(
- isFocused ? theme.menuSelectionBackground : Colors.transparentWhite,
- ),
];
let placeholder = (~height) => [Style.height(height)];
@@ -91,31 +88,20 @@ module Make = (Model: TreeModel) => {
let rec nodeView =
(
~renderContent,
- ~focus,
~itemHeight,
~clipRange as (clipStart, clipEnd),
~onClick,
~node,
- ~theme,
(),
) => {
let subtreeSize = Model.expandedSubtreeSize(node);
- let (isFocused, childFocus) =
- switch (focus) {
- | Some([last]) when last == node => (true, None)
- | Some([head, ...tail]) when head == node => (false, Some(tail))
- | Some(_) => (false, None)
- | _ => (false, None)
- };
-
let placeholder = (~size, ()) =>
;
let item = (~arrow, ()) =>
onClick(node)}
- style={Styles.item(~itemHeight, ~isFocused, ~theme)}>
+ onClick={() => onClick(node)} style={Styles.item(~itemHeight)}>
{renderContent(node)}
;
@@ -127,12 +113,10 @@ module Make = (Model: TreeModel) => {
let element =
;
loop(
@@ -173,7 +157,6 @@ module Make = (Model: TreeModel) => {
let%component make =
(
~children as renderContent,
- ~focus: option(list(Model.t)),
~itemHeight,
~initialRowsToRender=10,
~onClick,
@@ -181,7 +164,6 @@ module Make = (Model: TreeModel) => {
~onScrollOffsetChange:
[ | `Start(float) | `Middle(float)] => unit=_ => (),
~tree,
- ~theme,
(),
) => {
let%hook (outerRef, setOuterRef) = Hooks.ref(None);
@@ -264,15 +246,7 @@ module Make = (Model: TreeModel) => {
onMouseWheel=scroll>
-
+
{showScrollbar ? : React.empty}