From 26262aaf05983c5b7f41cc438e287523268fe1eb Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 10 Aug 2018 22:23:17 +0300 Subject: extend selection via LSP --- codeless/package.json | 7 ++++ codeless/server/src/dispatch.rs | 23 ++++++++++- codeless/server/src/handlers.rs | 58 ++++++++++++++++++++++++--- codeless/server/src/main.rs | 50 +++++++++++++---------- codeless/server/src/req.rs | 9 ++++- libanalysis/Cargo.toml | 2 + libanalysis/src/arena.rs | 42 ------------------- libanalysis/src/lib.rs | 89 +++++++++++++++++++++++++---------------- libeditor/src/lib.rs | 1 + libeditor/src/line_index.rs | 40 ++++++++++-------- 10 files changed, 198 insertions(+), 123 deletions(-) delete mode 100644 libanalysis/src/arena.rs diff --git a/codeless/package.json b/codeless/package.json index 7a00f33ef..1df932db1 100644 --- a/codeless/package.json +++ b/codeless/package.json @@ -36,6 +36,13 @@ "command": "libsyntax-rust.extendSelection", "title": "Rust Extend Selection" } + ], + "keybindings": [ + { + "command": "libsyntax-rust.extendSelection", + "key": "ctrl+w", + "when": "editorTextFocus && editorLangId == rust" + } ] } } diff --git a/codeless/server/src/dispatch.rs b/codeless/server/src/dispatch.rs index 41437b62a..2da0996e3 100644 --- a/codeless/server/src/dispatch.rs +++ b/codeless/server/src/dispatch.rs @@ -52,7 +52,7 @@ impl Responder } -pub fn parse_request_as(raw: RawRequest) -> Result<::std::result::Result<(R::Params, Responder), RawRequest>> +fn parse_request_as(raw: RawRequest) -> Result<::std::result::Result<(R::Params, Responder), RawRequest>> where R: Request, R::Params: DeserializeOwned, @@ -71,6 +71,25 @@ pub fn parse_request_as(raw: RawRequest) -> Result<::std::result::Result<(R:: Ok(Ok((params, responder))) } +pub fn handle_request(req: &mut Option, f: F) -> Result<()> + where + R: Request, + R::Params: DeserializeOwned, + R::Result: Serialize, + F: FnOnce(R::Params, Responder) -> Result<()> +{ + match req.take() { + None => Ok(()), + Some(r) => match parse_request_as::(r)? { + Ok((params, responder)) => f(params, responder), + Err(r) => { + *req = Some(r); + Ok(()) + }, + } + } +} + pub fn expect_request(io: &mut Io, raw: RawRequest) -> Result)>> where R: Request, @@ -87,7 +106,7 @@ pub fn expect_request(io: &mut Io, raw: RawRequest) -> Result(raw: RawNotification) -> Result<::std::result::Result> +fn parse_notification_as(raw: RawNotification) -> Result<::std::result::Result> where N: Notification, N::Params: DeserializeOwned, diff --git a/codeless/server/src/handlers.rs b/codeless/server/src/handlers.rs index 3f257941a..5ee87a4dd 100644 --- a/codeless/server/src/handlers.rs +++ b/codeless/server/src/handlers.rs @@ -1,13 +1,61 @@ +use languageserver_types::{Range, Position}; use libanalysis::World; -use libeditor; -use {req, Result}; +use libeditor::{self, LineIndex, LineCol, TextRange, TextUnit}; +use {req, Result, FilePath}; pub fn handle_syntax_tree( world: World, - params: req::SyntaxTreeParams + params: req::SyntaxTreeParams, ) -> Result { - let path = params.text_document.uri.to_file_path() - .map_err(|()| format_err!("invalid path"))?; + let path = params.text_document.file_path()?; let file = world.file_syntax(&path)?; Ok(libeditor::syntax_tree(&file)) } + +pub fn handle_extend_selection( + world: World, + params: req::ExtendSelectionParams, +) -> Result { + let path = params.text_document.file_path()?; + let file = world.file_syntax(&path)?; + let line_index = world.file_line_index(&path)?; + let selections = params.selections.into_iter() + .map(|r| { + let r = to_text_range(&line_index, r); + let r = libeditor::extend_selection(&file, r).unwrap_or(r); + to_vs_range(&line_index, r) + }) + .collect(); + Ok(req::ExtendSelectionResult { selections }) +} + + +fn to_text_range(line_index: &LineIndex, range: Range) -> TextRange { + TextRange::from_to( + to_text_unit(line_index, range.start), + to_text_unit(line_index, range.end), + ) +} + +fn to_text_unit(line_index: &LineIndex, position: Position) -> TextUnit { + // TODO: UTF-16 + let line_col = LineCol { + line: position.line as u32, + col: (position.character as u32).into(), + }; + line_index.offset(line_col) +} + + +fn to_vs_range(line_index: &LineIndex, range: TextRange) -> Range { + Range::new( + to_vs_position(line_index, range.start()), + to_vs_position(line_index, range.end()), + ) +} + +fn to_vs_position(line_index: &LineIndex, offset: TextUnit) -> Position { + let line_col = line_index.line_col(offset); + // TODO: UTF-16 + Position::new(line_col.line as u64, u32::from(line_col.col) as u64) +} diff --git a/codeless/server/src/main.rs b/codeless/server/src/main.rs index 287d650fa..116abce1c 100644 --- a/codeless/server/src/main.rs +++ b/codeless/server/src/main.rs @@ -31,7 +31,7 @@ use languageserver_types::{TextDocumentItem, VersionedTextDocumentIdentifier, Te use ::{ io::{Io, RawMsg}, - handlers::handle_syntax_tree, + handlers::{handle_syntax_tree, handle_extend_selection}, }; pub type Result = ::std::result::Result; @@ -153,34 +153,42 @@ fn main_loop( match msg { RawMsg::Request(req) => { - let req = match dispatch::parse_request_as::(req)? { - Ok((params, resp)) => { - let world = world.snapshot(); - let sender = sender.clone(); - pool.execute(move || { - let res: Result = handle_syntax_tree(world, params); - sender.send(Box::new(|io: &mut Io| resp.response(io, res))) - }); - continue; - } - Err(req) => req, - }; - - if let Some(((), resp)) = dispatch::expect_request::(io, req)? { - info!("clean shutdown started"); + let mut req = Some(req); + dispatch::handle_request::(&mut req, |params, resp| { + let world = world.snapshot(); + let sender = sender.clone(); + pool.execute(move || { + let res = handle_syntax_tree(world, params); + sender.send(Box::new(|io: &mut Io| resp.response(io, res))) + }); + Ok(()) + })?; + dispatch::handle_request::(&mut req, |params, resp| { + let world = world.snapshot(); + let sender = sender.clone(); + pool.execute(move || { + let res = handle_extend_selection(world, params); + sender.send(Box::new(|io: &mut Io| resp.response(io, res))) + }); + Ok(()) + })?; + dispatch::handle_request::(&mut req, |(), resp| { resp.result(io, ())?; - return Ok(()); + Ok(()) + })?; + if let Some(req) = req { + error!("unknown method: {:?}", req); + dispatch::unknown_method(io, req)?; } } RawMsg::Notification(not) => { - use dispatch::handle_notification as h; let mut not = Some(not); - h::(&mut not, |params| { + dispatch::handle_notification::(&mut not, |params| { let path = params.text_document.file_path()?; world.change_overlay(path, Some(params.text_document.text)); Ok(()) })?; - h::(&mut not, |mut params| { + dispatch::handle_notification::(&mut not, |mut params| { let path = params.text_document.file_path()?; let text = params.content_changes.pop() .ok_or_else(|| format_err!("empty changes"))? @@ -188,7 +196,7 @@ fn main_loop( world.change_overlay(path, Some(text)); Ok(()) })?; - h::(&mut not, |params| { + dispatch::handle_notification::(&mut not, |params| { let path = params.text_document.file_path()?; world.change_overlay(path, None); Ok(()) diff --git a/codeless/server/src/req.rs b/codeless/server/src/req.rs index ee4a786c7..4e588159b 100644 --- a/codeless/server/src/req.rs +++ b/codeless/server/src/req.rs @@ -21,6 +21,12 @@ pub struct SyntaxTreeParams { pub enum ExtendSelection {} +impl Request for ExtendSelection { + type Params = ExtendSelectionParams; + type Result = ExtendSelectionResult; + const METHOD: &'static str = "m/extendSelection"; +} + #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct ExtendSelectionParams { @@ -28,7 +34,8 @@ pub struct ExtendSelectionParams { pub selections: Vec, } - +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] pub struct ExtendSelectionResult { pub selections: Vec, } diff --git a/libanalysis/Cargo.toml b/libanalysis/Cargo.toml index 737463258..2beea5640 100644 --- a/libanalysis/Cargo.toml +++ b/libanalysis/Cargo.toml @@ -7,4 +7,6 @@ authors = ["Aleksey Kladov "] log = "0.4.2" failure = "0.1.2" parking_lot = "0.6.3" +once_cell = "0.1.4" libsyntax2 = { path = "../" } +libeditor = { path = "../libeditor" } diff --git a/libanalysis/src/arena.rs b/libanalysis/src/arena.rs deleted file mode 100644 index fc0c25c54..000000000 --- a/libanalysis/src/arena.rs +++ /dev/null @@ -1,42 +0,0 @@ -use parking_lot::RwLock; - -const CHUNK_LEN: usize = 16; - -pub struct Arena { - chunks: RwLock>>, -} - -impl Arena { - pub fn new(&self) -> Arena { - Arena { - chunks: RwLock::new(vec![Vec::with_capacity(CHUNK_LEN)]), - } - } - - pub fn push(&self, value: T) -> usize { - let mut guard = self.chunks.write(); - let mut idx = (guard.len() - 1) * CHUNK_LEN; - let chunk = { - if guard.last().unwrap().len() == CHUNK_LEN { - guard.push(Vec::with_capacity(CHUNK_LEN)); - } - guard.last_mut().unwrap() - }; - assert!(chunk.len() < chunk.capacity()); - idx += chunk.len(); - chunk.push(value); - idx - } - - pub fn get(&self, idx: usize) -> &T { - let chunk_idx = idx / CHUNK_LEN; - let chunk_off = idx - chunk_idx * CHUNK_LEN; - let guard = self.chunks.read(); - let value = &guard[chunk_idx][chunk_off]; - unsafe { - // We are careful to not move values in chunks, - // so this hopefully is safe - ::std::mem::transmute::<&T, &T>(value) - } - } -} diff --git a/libanalysis/src/lib.rs b/libanalysis/src/lib.rs index 417a544ca..6a946a0b0 100644 --- a/libanalysis/src/lib.rs +++ b/libanalysis/src/lib.rs @@ -2,9 +2,11 @@ extern crate failure; extern crate parking_lot; #[macro_use] extern crate log; +extern crate once_cell; extern crate libsyntax2; +extern crate libeditor; -mod arena; +use once_cell::sync::OnceCell; use std::{ fs, @@ -14,6 +16,7 @@ use std::{ }; use parking_lot::RwLock; use libsyntax2::ast; +use libeditor::LineIndex; pub type Result = ::std::result::Result; @@ -39,7 +42,6 @@ impl WorldState { pub fn change_overlay(&mut self, path: PathBuf, text: Option) { let data = self.data_mut(); data.file_map.get_mut().remove(&path); - data.fs_map.get_mut().remove(&path); if let Some(text) = text { data.mem_map.insert(path, Arc::new(text)); } else { @@ -49,11 +51,9 @@ impl WorldState { fn data_mut(&mut self) -> &mut WorldData { if Arc::get_mut(&mut self.data).is_none() { - let fs_map = self.data.fs_map.read().clone(); let file_map = self.data.file_map.read().clone(); self.data = Arc::new(WorldData { mem_map: self.data.mem_map.clone(), - fs_map: RwLock::new(fs_map), file_map: RwLock::new(file_map), }); } @@ -64,43 +64,57 @@ impl WorldState { impl World { pub fn file_syntax(&self, path: &Path) -> Result { - { - let guard = self.data.file_map.read(); - if let Some(file) = guard.get(path) { - return Ok(file.clone()); - } - } - let file = self.with_file_text(path, |text| { - trace!("parsing file: {}", path.display()); - ast::File::parse(text) - })?; - let mut guard = self.data.file_map.write(); - let file = guard.entry(path.to_owned()) - .or_insert(file) - .clone(); - Ok(file) + let data = self.file_data(path)?; + let syntax = data.syntax + .get_or_init(|| { + trace!("parsing: {}", path.display()); + ast::File::parse(self.file_text(path, &data)) + }).clone(); + Ok(syntax) } - fn with_file_text R, R>(&self, path: &Path, f: F) -> Result { - if let Some(text) = self.data.mem_map.get(path) { - return Ok(f(&*text)); + pub fn file_line_index(&self, path: &Path) -> Result { + let data = self.file_data(path)?; + let index = data.lines + .get_or_init(|| { + trace!("calc line index: {}", path.display()); + LineIndex::new(self.file_text(path, &data)) + }); + Ok(index.clone()) + } + + fn file_text<'a>(&'a self, path: &Path, file_data: &'a FileData) -> &'a str { + match file_data.text.as_ref() { + Some(text) => text.as_str(), + None => self.data.mem_map[path].as_str() } + } + fn file_data(&self, path: &Path) -> Result> { { - let guard = self.data.fs_map.read(); - if let Some(text) = guard.get(path) { - return Ok(f(&*text)); + let guard = self.data.file_map.read(); + if let Some(data) = guard.get(path) { + return Ok(data.clone()); } } - trace!("loading file from disk: {}", path.display()); - let text = fs::read_to_string(path)?; - { - let mut guard = self.data.fs_map.write(); + + let text = if self.data.mem_map.contains_key(path) { + None + } else { + trace!("loading file from disk: {}", path.display()); + Some(fs::read_to_string(path)?) + }; + let res = { + let mut guard = self.data.file_map.write(); guard.entry(path.to_owned()) - .or_insert_with(|| Arc::new(text)); - } - let guard = self.data.fs_map.read(); - Ok(f(&guard[path])) + .or_insert_with(|| Arc::new(FileData { + text, + syntax: OnceCell::new(), + lines: OnceCell::new(), + })) + .clone() + }; + Ok(res) } } @@ -108,6 +122,11 @@ impl World { #[derive(Default)] struct WorldData { mem_map: HashMap>, - fs_map: RwLock>>, - file_map: RwLock>, + file_map: RwLock>>, +} + +struct FileData { + text: Option, + syntax: OnceCell, + lines: OnceCell, } diff --git a/libeditor/src/lib.rs b/libeditor/src/lib.rs index 9da71743f..f77647338 100644 --- a/libeditor/src/lib.rs +++ b/libeditor/src/lib.rs @@ -10,6 +10,7 @@ use libsyntax2::{ SyntaxKind::*, }; pub use libsyntax2::{TextRange, TextUnit, ast}; +pub use self::line_index::{LineIndex, LineCol}; #[derive(Debug)] pub struct HighlightedRange { diff --git a/libeditor/src/line_index.rs b/libeditor/src/line_index.rs index feb482b32..801726aa5 100644 --- a/libeditor/src/line_index.rs +++ b/libeditor/src/line_index.rs @@ -1,6 +1,7 @@ use superslice::Ext; -use ::{TextUnit}; +use ::TextUnit; +#[derive(Clone, Debug)] pub struct LineIndex { newlines: Vec, } @@ -24,11 +25,16 @@ impl LineIndex { LineIndex { newlines } } - pub fn translate(&self, offset: TextUnit) -> LineCol { + pub fn line_col(&self, offset: TextUnit) -> LineCol { let line = self.newlines.upper_bound(&offset) - 1; let line_start_offset = self.newlines[line]; let col = offset - line_start_offset; - return LineCol { line: line as u32, col } + return LineCol { line: line as u32, col }; + } + + pub fn offset(&self, line_col: LineCol) -> TextUnit { + //TODO: return Result + self.newlines[line_col.line as usize] + line_col.col } } @@ -36,21 +42,21 @@ impl LineIndex { fn test_line_index() { let text = "hello\nworld"; let index = LineIndex::new(text); - assert_eq!(index.translate(0.into()), LineCol { line: 0, col: 0.into()}); - assert_eq!(index.translate(1.into()), LineCol { line: 0, col: 1.into()}); - assert_eq!(index.translate(5.into()), LineCol { line: 0, col: 5.into()}); - assert_eq!(index.translate(6.into()), LineCol { line: 1, col: 0.into()}); - assert_eq!(index.translate(7.into()), LineCol { line: 1, col: 1.into()}); - assert_eq!(index.translate(8.into()), LineCol { line: 1, col: 2.into()}); - assert_eq!(index.translate(10.into()), LineCol { line: 1, col: 4.into()}); - assert_eq!(index.translate(11.into()), LineCol { line: 1, col: 5.into()}); - assert_eq!(index.translate(12.into()), LineCol { line: 1, col: 6.into()}); + assert_eq!(index.line_col(0.into()), LineCol { line: 0, col: 0.into() }); + assert_eq!(index.line_col(1.into()), LineCol { line: 0, col: 1.into() }); + assert_eq!(index.line_col(5.into()), LineCol { line: 0, col: 5.into() }); + assert_eq!(index.line_col(6.into()), LineCol { line: 1, col: 0.into() }); + assert_eq!(index.line_col(7.into()), LineCol { line: 1, col: 1.into() }); + assert_eq!(index.line_col(8.into()), LineCol { line: 1, col: 2.into() }); + assert_eq!(index.line_col(10.into()), LineCol { line: 1, col: 4.into() }); + assert_eq!(index.line_col(11.into()), LineCol { line: 1, col: 5.into() }); + assert_eq!(index.line_col(12.into()), LineCol { line: 1, col: 6.into() }); let text = "\nhello\nworld"; let index = LineIndex::new(text); - assert_eq!(index.translate(0.into()), LineCol { line: 0, col: 0.into()}); - assert_eq!(index.translate(1.into()), LineCol { line: 1, col: 0.into()}); - assert_eq!(index.translate(2.into()), LineCol { line: 1, col: 1.into()}); - assert_eq!(index.translate(6.into()), LineCol { line: 1, col: 5.into()}); - assert_eq!(index.translate(7.into()), LineCol { line: 2, col: 0.into()}); + assert_eq!(index.line_col(0.into()), LineCol { line: 0, col: 0.into() }); + assert_eq!(index.line_col(1.into()), LineCol { line: 1, col: 0.into() }); + assert_eq!(index.line_col(2.into()), LineCol { line: 1, col: 1.into() }); + assert_eq!(index.line_col(6.into()), LineCol { line: 1, col: 5.into() }); + assert_eq!(index.line_col(7.into()), LineCol { line: 2, col: 0.into() }); } -- cgit v1.2.3