From 63a401ac5da18001a2dbbbbe18a5dd74f9c4bfd3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 12 Jan 2022 18:17:19 -0800 Subject: [PATCH] Add Buffer::outline method Co-Authored-By: Nathan Sobo --- Cargo.lock | 12 ++++ crates/editor/src/multi_buffer.rs | 6 +- crates/language/src/buffer.rs | 96 ++++++++++++++++++++++++++- crates/language/src/language.rs | 14 ++++ crates/language/src/outline.rs | 12 ++++ crates/outline/Cargo.toml | 14 ++++ crates/outline/src/outline.rs | 53 +++++++++++++++ crates/zed/Cargo.toml | 1 + crates/zed/languages/rust/outline.scm | 17 +++++ crates/zed/src/language.rs | 2 + crates/zed/src/main.rs | 1 + 11 files changed, 224 insertions(+), 4 deletions(-) create mode 100644 crates/language/src/outline.rs create mode 100644 crates/outline/Cargo.toml create mode 100644 crates/outline/src/outline.rs create mode 100644 crates/zed/languages/rust/outline.scm diff --git a/Cargo.lock b/Cargo.lock index 8c3174d68d40df5c364bee8327318476b8dd0fa7..f1761a5daaa2eb4d9f09dfac36f6a63a5ee91bb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3121,6 +3121,17 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85" +[[package]] +name = "outline" +version = "0.1.0" +dependencies = [ + "editor", + "gpui", + "postage", + "text", + "workspace", +] + [[package]] name = "p256" version = "0.9.0" @@ -5724,6 +5735,7 @@ dependencies = [ "log-panics", "lsp", "num_cpus", + "outline", "parking_lot", "postage", "project", diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index c7192cd622c51dbd43dbf58f5f561e045f64a2b9..54a44d2aa77555251458c731cdf266c97869d1d8 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -7,7 +7,7 @@ use collections::{HashMap, HashSet}; use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; use language::{ Buffer, BufferChunks, BufferSnapshot, Chunk, DiagnosticEntry, Event, File, Language, Selection, - ToOffset as _, ToPoint as _, TransactionId, + ToOffset as _, ToPoint as _, TransactionId, Outline, }; use std::{ cell::{Ref, RefCell}, @@ -1698,6 +1698,10 @@ impl MultiBufferSnapshot { }) } + pub fn outline(&self) -> Option { + self.as_singleton().and_then(move |buffer| buffer.outline()) + } + fn buffer_snapshot_for_excerpt<'a>( &'a self, excerpt_id: &'a ExcerptId, diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 07d4017c09eb14f64a7b9463b29247f436154403..515def357f8aa089f9eae1d816ffd1c089e4b69f 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -6,7 +6,8 @@ pub use crate::{ }; use crate::{ diagnostic_set::{DiagnosticEntry, DiagnosticGroup}, - range_from_lsp, + outline::OutlineItem, + range_from_lsp, Outline, }; use anyhow::{anyhow, Result}; use clock::ReplicaId; @@ -193,7 +194,7 @@ pub trait File { fn as_any(&self) -> &dyn Any; } -struct QueryCursorHandle(Option); +pub(crate) struct QueryCursorHandle(Option); #[derive(Clone)] struct SyntaxTree { @@ -1264,6 +1265,13 @@ impl Buffer { self.edit_internal(ranges_iter, new_text, true, cx) } + /* + impl Buffer + pub fn edit + pub fn edit_internal + pub fn edit_with_autoindent + */ + pub fn edit_internal( &mut self, ranges_iter: I, @@ -1827,6 +1835,82 @@ impl BufferSnapshot { } } + pub fn outline(&self) -> Option { + let tree = self.tree.as_ref()?; + let grammar = self + .language + .as_ref() + .and_then(|language| language.grammar.as_ref())?; + + let mut cursor = QueryCursorHandle::new(); + let matches = cursor.matches( + &grammar.outline_query, + tree.root_node(), + TextProvider(self.as_rope()), + ); + + let item_capture_ix = grammar.outline_query.capture_index_for_name("item")?; + let context_capture_ix = grammar.outline_query.capture_index_for_name("context")?; + let name_capture_ix = grammar.outline_query.capture_index_for_name("name")?; + + let mut id = 0; + let mut items = matches + .filter_map(|mat| { + let item_node = mat.nodes_for_capture_index(item_capture_ix).next()?; + let mut name_node = Some(mat.nodes_for_capture_index(name_capture_ix).next()?); + let mut context_nodes = mat.nodes_for_capture_index(context_capture_ix).peekable(); + + let id = post_inc(&mut id); + let range = item_node.start_byte()..item_node.end_byte(); + + let mut text = String::new(); + let mut name_range_in_text = 0..0; + loop { + let node; + let node_is_name; + match (context_nodes.peek(), name_node.as_ref()) { + (None, None) => break, + (None, Some(_)) => { + node = name_node.take().unwrap(); + node_is_name = true; + } + (Some(_), None) => { + node = context_nodes.next().unwrap(); + node_is_name = false; + } + (Some(context_node), Some(name)) => { + if context_node.start_byte() < name.start_byte() { + node = context_nodes.next().unwrap(); + node_is_name = false; + } else { + node = name_node.take().unwrap(); + node_is_name = true; + } + } + } + + if !text.is_empty() { + text.push(' '); + } + let range = node.start_byte()..node.end_byte(); + if node_is_name { + name_range_in_text = text.len()..(text.len() + range.len()) + } + text.extend(self.text_for_range(range)); + } + + Some(OutlineItem { + id, + range, + text, + name_range_in_text, + }) + }) + .collect::>(); + + Some(Outline(items)) + } + pub fn enclosing_bracket_ranges( &self, range: Range, @@ -1854,6 +1938,12 @@ impl BufferSnapshot { .min_by_key(|(open_range, close_range)| close_range.end - open_range.start) } + /* + impl BufferSnapshot + pub fn remote_selections_in_range(&self, Range) -> impl Iterator>)> + pub fn remote_selections_in_range(&self, Range) -> impl Iterator( &'a self, range: Range, @@ -2108,7 +2198,7 @@ impl<'a> Iterator for BufferChunks<'a> { } impl QueryCursorHandle { - fn new() -> Self { + pub(crate) fn new() -> Self { QueryCursorHandle(Some( QUERY_CURSORS .lock() diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 769bcbe69c03de41a4e61a417707a7c63dff9f62..56bcfbc67c6137fb71a612952845bdf022301e53 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -1,6 +1,7 @@ mod buffer; mod diagnostic_set; mod highlight_map; +mod outline; pub mod proto; #[cfg(test)] mod tests; @@ -13,6 +14,7 @@ pub use diagnostic_set::DiagnosticEntry; use gpui::AppContext; use highlight_map::HighlightMap; use lazy_static::lazy_static; +pub use outline::Outline; use parking_lot::Mutex; use serde::Deserialize; use std::{ops::Range, path::Path, str, sync::Arc}; @@ -74,6 +76,7 @@ pub struct Grammar { pub(crate) highlights_query: Query, pub(crate) brackets_query: Query, pub(crate) indents_query: Query, + pub(crate) outline_query: Query, pub(crate) highlight_map: Mutex, } @@ -127,6 +130,7 @@ impl Language { brackets_query: Query::new(ts_language, "").unwrap(), highlights_query: Query::new(ts_language, "").unwrap(), indents_query: Query::new(ts_language, "").unwrap(), + outline_query: Query::new(ts_language, "").unwrap(), ts_language, highlight_map: Default::default(), }) @@ -164,6 +168,16 @@ impl Language { Ok(self) } + pub fn with_outline_query(mut self, source: &str) -> Result { + let grammar = self + .grammar + .as_mut() + .and_then(Arc::get_mut) + .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?; + grammar.outline_query = Query::new(grammar.ts_language, source)?; + Ok(self) + } + pub fn name(&self) -> &str { self.config.name.as_str() } diff --git a/crates/language/src/outline.rs b/crates/language/src/outline.rs new file mode 100644 index 0000000000000000000000000000000000000000..427b91d46419fd9fab0bac1f31592853021fcae4 --- /dev/null +++ b/crates/language/src/outline.rs @@ -0,0 +1,12 @@ +use std::ops::Range; + +#[derive(Debug)] +pub struct Outline(pub Vec); + +#[derive(Debug)] +pub struct OutlineItem { + pub id: usize, + pub range: Range, + pub text: String, + pub name_range_in_text: Range, +} diff --git a/crates/outline/Cargo.toml b/crates/outline/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..9ee0c76f7d3da8e2acf9b4e8d61f60d3dff8564e --- /dev/null +++ b/crates/outline/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "outline" +version = "0.1.0" +edition = "2021" + +[lib] +path = "src/outline.rs" + +[dependencies] +text = { path = "../text" } +editor = { path = "../editor" } +gpui = { path = "../gpui" } +workspace = { path = "../workspace" } +postage = { version = "0.4", features = ["futures-traits"] } diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs new file mode 100644 index 0000000000000000000000000000000000000000..e9507296e78f0df289ccb314dd13d4b8e058c650 --- /dev/null +++ b/crates/outline/src/outline.rs @@ -0,0 +1,53 @@ +use editor::{display_map::ToDisplayPoint, Autoscroll, Editor, EditorSettings}; +use gpui::{ + action, elements::*, geometry::vector::Vector2F, keymap::Binding, Axis, Entity, + MutableAppContext, RenderContext, View, ViewContext, ViewHandle, +}; +use postage::watch; +use std::sync::Arc; +use text::{Bias, Point, Selection}; +use workspace::{Settings, Workspace}; + +action!(Toggle); +action!(Confirm); + +pub fn init(cx: &mut MutableAppContext) { + cx.add_bindings([ + Binding::new("cmd-shift-O", Toggle, Some("Editor")), + Binding::new("escape", Toggle, Some("GoToLine")), + Binding::new("enter", Confirm, Some("GoToLine")), + ]); + cx.add_action(OutlineView::toggle); + cx.add_action(OutlineView::confirm); +} + +struct OutlineView {} + +impl Entity for OutlineView { + type Event = (); +} + +impl View for OutlineView { + fn ui_name() -> &'static str { + "OutlineView" + } + + fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox { + todo!() + } +} + +impl OutlineView { + fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { + let editor = workspace + .active_item(cx) + .unwrap() + .to_any() + .downcast::() + .unwrap(); + let buffer = editor.read(cx).buffer().read(cx); + dbg!(buffer.read(cx).outline()); + } + + fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) {} +} diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 66f6f68c9bb470bacb2e9684efd7b83a69b02921..a4a7252a808c9dd62bb6ecfefbf6c8a6e2555d33 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -43,6 +43,7 @@ gpui = { path = "../gpui" } journal = { path = "../journal" } language = { path = "../language" } lsp = { path = "../lsp" } +outline = { path = "../outline" } project = { path = "../project" } project_panel = { path = "../project_panel" } rpc = { path = "../rpc" } diff --git a/crates/zed/languages/rust/outline.scm b/crates/zed/languages/rust/outline.scm new file mode 100644 index 0000000000000000000000000000000000000000..3f3cf62fc09352231508dc5ae7f29b5a48a09105 --- /dev/null +++ b/crates/zed/languages/rust/outline.scm @@ -0,0 +1,17 @@ +(impl_item + "impl" @context + type: (_) @name) @item + +(function_item + (visibility_modifier)? @context + "fn" @context + name: (identifier) @name) @item + +(struct_item + (visibility_modifier)? @context + "struct" @context + name: (type_identifier) @name) @item + +(field_declaration + (visibility_modifier)? @context + name: (field_identifier) @name) @item diff --git a/crates/zed/src/language.rs b/crates/zed/src/language.rs index a84d2cbd40b7a9d16734056e29ce79c18a173bff..98f6ab93d27675f391ee9164649490a055d3066e 100644 --- a/crates/zed/src/language.rs +++ b/crates/zed/src/language.rs @@ -24,6 +24,8 @@ fn rust() -> Language { .unwrap() .with_indents_query(load_query("rust/indents.scm").as_ref()) .unwrap() + .with_outline_query(load_query("rust/outline.scm").as_ref()) + .unwrap() } fn markdown() -> Language { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f34c700c54b3771f683013c9d01a9d49c5651cb1..59804cf87c1bc8e8a6a8f2ea12c44faabdc10a02 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -59,6 +59,7 @@ fn main() { go_to_line::init(cx); file_finder::init(cx); chat_panel::init(cx); + outline::init(cx); project_panel::init(cx); diagnostics::init(cx); cx.spawn({