From 8a0ab447ecfa6c2c448603d469ba4fd06e95c754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Wed, 24 Mar 2021 14:28:26 +0900 Subject: [PATCH] editor.open can now either replace the current view or open in a split. --- helix-term/src/application.rs | 3 +- helix-term/src/commands.rs | 9 ++- helix-term/src/ui/mod.rs | 5 +- helix-view/src/editor.rs | 103 ++++++++++++++++++++-------------- helix-view/src/tree.rs | 86 ++++++++++++++++++++++++++-- 5 files changed, 153 insertions(+), 53 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 38c0d88a8..dcc6433b7 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -39,7 +39,8 @@ impl Application { let files = args.values_of_t::("files").unwrap(); for file in files { - editor.open(file)?; + use helix_view::editor::Action; + editor.open(file, Action::HorizontalSplit)?; } compositor.push(Box::new(ui::EditorView::new())); diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 9544b0e06..12d80a0f8 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -798,7 +798,8 @@ pub fn command_mode(cx: &mut Context) { // editor.should_close = true, } ["o", path] | ["open", path] => { - editor.open(path.into()); + use helix_view::editor::Action; + editor.open(path.into(), Action::Replace); } ["w"] | ["write"] => { // TODO: non-blocking via save() command @@ -992,11 +993,13 @@ pub fn exit_select_mode(cx: &mut Context) { } fn goto(cx: &mut Context, locations: Vec) { + use helix_view::editor::Action; cx.doc().mode = Mode::Normal; match locations.as_slice() { [location] => { - cx.editor.open(PathBuf::from(location.uri.path())); + cx.editor + .open(PathBuf::from(location.uri.path()), Action::Replace); let doc = cx.doc(); let definition_pos = location.range.start; let new_pos = helix_lsp::util::lsp_pos_to_pos(doc.text(), definition_pos); @@ -1012,7 +1015,7 @@ fn goto(cx: &mut Context, locations: Vec) { format!("{}:{}", file, line).into() }, move |editor: &mut Editor, item| { - editor.open(PathBuf::from(item.uri.path())); + editor.open(PathBuf::from(item.uri.path()), Action::Replace); // TODO: issues with doc already being broo let id = editor.view().doc; let doc = &mut editor.documents[id]; diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index f91a9f35c..a625aa14c 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -101,7 +101,10 @@ pub fn file_picker(root: &str) -> Picker { path.strip_prefix("./").unwrap().to_str().unwrap().into() }, move |editor: &mut Editor, path: &PathBuf| { - let document_id = editor.open(path.into()).expect("editor.open failed"); + use helix_view::editor::Action; + let document_id = editor + .open(path.into(), Action::Replace) + .expect("editor.open failed"); }, ) } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 0407f344e..a2c3c101a 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -16,6 +16,12 @@ pub struct Editor { pub executor: &'static smol::Executor<'static>, } +pub enum Action { + Replace, + HorizontalSplit, + VerticalSplit, +} + impl Editor { pub fn new(executor: &'static smol::Executor<'static>, mut area: tui::layout::Rect) -> Self { let theme = Theme::default(); @@ -41,50 +47,63 @@ impl Editor { } } - pub fn open(&mut self, path: PathBuf) -> Result { - let existing_view = self.documents().find(|doc| doc.path() == Some(&path)); - - // TODO: - // if view with doc, focus it - // else open new split - - // if let Some((view, _)) = existing_view { - // let id = view.doc.id; - // self.tree.focus = view.id; - // return Ok(id); - // } - - let mut doc = Document::load(path, self.theme.scopes())?; - - // try to find a language server based on the language name - let language_server = doc - .language - .as_ref() - .and_then(|language| self.language_servers.get(language, self.executor)); - - if let Some(language_server) = language_server { - doc.set_language_server(Some(language_server.clone())); - - let language_id = doc - .language() - .and_then(|s| s.split('.').last()) // source.rust - .map(ToOwned::to_owned) - .unwrap_or_default(); - - smol::block_on(language_server.text_document_did_open( - doc.url().unwrap(), - doc.version(), - doc.text(), - language_id, - )) - .unwrap(); + pub fn open(&mut self, path: PathBuf, action: Action) -> Result { + let id = self + .documents() + .find(|doc| doc.path() == Some(&path)) + .map(|doc| doc.id); + + let id = if let Some(id) = id { + id + } else { + let mut doc = Document::load(path, self.theme.scopes())?; + + // try to find a language server based on the language name + let language_server = doc + .language + .as_ref() + .and_then(|language| self.language_servers.get(language, self.executor)); + + if let Some(language_server) = language_server { + doc.set_language_server(Some(language_server.clone())); + + let language_id = doc + .language() + .and_then(|s| s.split('.').last()) // source.rust + .map(ToOwned::to_owned) + .unwrap_or_default(); + + smol::block_on(language_server.text_document_did_open( + doc.url().unwrap(), + doc.version(), + doc.text(), + language_id, + )) + .unwrap(); + } + + let id = self.documents.insert(doc); + self.documents[id].id = id; + id + }; + + use crate::tree::Layout; + match action { + Action::Replace => { + self.view_mut().doc = id; + // TODO: reset selection? + return Ok(id); + } + Action::HorizontalSplit => { + let view = View::new(id)?; + self.tree.split(view, Layout::Horizontal); + } + Action::VerticalSplit => { + let view = View::new(id)?; + self.tree.split(view, Layout::Vertical); + } } - let id = self.documents.insert(doc); - self.documents[id].id = id; - - let view = View::new(id)?; - self.tree.insert(view); self._refresh(); Ok(id) diff --git a/helix-view/src/tree.rs b/helix-view/src/tree.rs index 4751f8400..f7ef7806d 100644 --- a/helix-view/src/tree.rs +++ b/helix-view/src/tree.rs @@ -28,10 +28,10 @@ pub enum Content { } impl Node { - pub fn container() -> Self { + pub fn container(layout: Layout) -> Self { Node { parent: ViewId::default(), - content: Content::Container(Box::new(Container::new())), + content: Content::Container(Box::new(Container::new(layout))), } } @@ -45,6 +45,7 @@ impl Node { // TODO: screen coord to container + container coordinate helpers +#[derive(PartialEq, Eq)] pub enum Layout { Horizontal, Vertical, @@ -58,9 +59,9 @@ pub struct Container { } impl Container { - pub fn new() -> Self { + pub fn new(layout: Layout) -> Self { Self { - layout: Layout::Horizontal, + layout, children: Vec::new(), area: Rect::default(), } @@ -69,13 +70,13 @@ impl Container { impl Default for Container { fn default() -> Self { - Self::new() + Self::new(Layout::Horizontal) } } impl Tree { pub fn new(area: Rect) -> Self { - let root = Node::container(); + let root = Node::container(Layout::Horizontal); let mut nodes = HopSlotMap::with_key(); let root = nodes.insert(root); @@ -131,6 +132,79 @@ impl Tree { node } + pub fn split(&mut self, view: View, layout: Layout) -> ViewId { + let focus = self.focus; + let parent = self.nodes[focus].parent; + + let node = Node::view(view); + let node = self.nodes.insert(node); + self.get_mut(node).id = node; + + let container = match &mut self.nodes[parent] { + Node { + content: Content::Container(container), + .. + } => container, + _ => unreachable!(), + }; + + if container.layout == layout { + // insert node after the current item if there is children already + let pos = if container.children.is_empty() { + 0 + } else { + let pos = container + .children + .iter() + .position(|&child| child == focus) + .unwrap(); + pos + 1 + }; + container.children.insert(pos, node); + self.nodes[node].parent = parent; + } else { + let split = Node::container(layout); + let split = self.nodes.insert(split); + + let container = match &mut self.nodes[split] { + Node { + content: Content::Container(container), + .. + } => container, + _ => unreachable!(), + }; + container.children.push(focus); + container.children.push(node); + self.nodes[focus].parent = split; + self.nodes[node].parent = split; + + let container = match &mut self.nodes[parent] { + Node { + content: Content::Container(container), + .. + } => container, + _ => unreachable!(), + }; + + let pos = container + .children + .iter() + .position(|&child| child == focus) + .unwrap(); + + // replace focus on parent with split + container.children[pos] = split; + } + + // focus the new node + self.focus = node; + + // recalculate all the sizes + self.recalculate(); + + node + } + pub fn remove(&mut self, index: ViewId) { let mut stack = Vec::new();