diff options
-rw-r--r-- | codeless/package.json | 7 | ||||
-rw-r--r-- | codeless/server/src/dispatch.rs | 23 | ||||
-rw-r--r-- | codeless/server/src/handlers.rs | 58 | ||||
-rw-r--r-- | codeless/server/src/main.rs | 50 | ||||
-rw-r--r-- | codeless/server/src/req.rs | 9 | ||||
-rw-r--r-- | libanalysis/Cargo.toml | 2 | ||||
-rw-r--r-- | libanalysis/src/arena.rs | 42 | ||||
-rw-r--r-- | libanalysis/src/lib.rs | 89 | ||||
-rw-r--r-- | libeditor/src/lib.rs | 1 | ||||
-rw-r--r-- | libeditor/src/line_index.rs | 40 |
10 files changed, 198 insertions, 123 deletions
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 @@ | |||
36 | "command": "libsyntax-rust.extendSelection", | 36 | "command": "libsyntax-rust.extendSelection", |
37 | "title": "Rust Extend Selection" | 37 | "title": "Rust Extend Selection" |
38 | } | 38 | } |
39 | ], | ||
40 | "keybindings": [ | ||
41 | { | ||
42 | "command": "libsyntax-rust.extendSelection", | ||
43 | "key": "ctrl+w", | ||
44 | "when": "editorTextFocus && editorLangId == rust" | ||
45 | } | ||
39 | ] | 46 | ] |
40 | } | 47 | } |
41 | } | 48 | } |
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<R: Request> Responder<R> | |||
52 | } | 52 | } |
53 | 53 | ||
54 | 54 | ||
55 | pub fn parse_request_as<R>(raw: RawRequest) -> Result<::std::result::Result<(R::Params, Responder<R>), RawRequest>> | 55 | fn parse_request_as<R>(raw: RawRequest) -> Result<::std::result::Result<(R::Params, Responder<R>), RawRequest>> |
56 | where | 56 | where |
57 | R: Request, | 57 | R: Request, |
58 | R::Params: DeserializeOwned, | 58 | R::Params: DeserializeOwned, |
@@ -71,6 +71,25 @@ pub fn parse_request_as<R>(raw: RawRequest) -> Result<::std::result::Result<(R:: | |||
71 | Ok(Ok((params, responder))) | 71 | Ok(Ok((params, responder))) |
72 | } | 72 | } |
73 | 73 | ||
74 | pub fn handle_request<R, F>(req: &mut Option<RawRequest>, f: F) -> Result<()> | ||
75 | where | ||
76 | R: Request, | ||
77 | R::Params: DeserializeOwned, | ||
78 | R::Result: Serialize, | ||
79 | F: FnOnce(R::Params, Responder<R>) -> Result<()> | ||
80 | { | ||
81 | match req.take() { | ||
82 | None => Ok(()), | ||
83 | Some(r) => match parse_request_as::<R>(r)? { | ||
84 | Ok((params, responder)) => f(params, responder), | ||
85 | Err(r) => { | ||
86 | *req = Some(r); | ||
87 | Ok(()) | ||
88 | }, | ||
89 | } | ||
90 | } | ||
91 | } | ||
92 | |||
74 | pub fn expect_request<R>(io: &mut Io, raw: RawRequest) -> Result<Option<(R::Params, Responder<R>)>> | 93 | pub fn expect_request<R>(io: &mut Io, raw: RawRequest) -> Result<Option<(R::Params, Responder<R>)>> |
75 | where | 94 | where |
76 | R: Request, | 95 | R: Request, |
@@ -87,7 +106,7 @@ pub fn expect_request<R>(io: &mut Io, raw: RawRequest) -> Result<Option<(R::Para | |||
87 | Ok(ret) | 106 | Ok(ret) |
88 | } | 107 | } |
89 | 108 | ||
90 | pub fn parse_notification_as<N>(raw: RawNotification) -> Result<::std::result::Result<N::Params, RawNotification>> | 109 | fn parse_notification_as<N>(raw: RawNotification) -> Result<::std::result::Result<N::Params, RawNotification>> |
91 | where | 110 | where |
92 | N: Notification, | 111 | N: Notification, |
93 | N::Params: DeserializeOwned, | 112 | 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 @@ | |||
1 | use languageserver_types::{Range, Position}; | ||
1 | use libanalysis::World; | 2 | use libanalysis::World; |
2 | use libeditor; | 3 | use libeditor::{self, LineIndex, LineCol, TextRange, TextUnit}; |
3 | use {req, Result}; | 4 | use {req, Result, FilePath}; |
4 | 5 | ||
5 | pub fn handle_syntax_tree( | 6 | pub fn handle_syntax_tree( |
6 | world: World, | 7 | world: World, |
7 | params: req::SyntaxTreeParams | 8 | params: req::SyntaxTreeParams, |
8 | ) -> Result<String> { | 9 | ) -> Result<String> { |
9 | let path = params.text_document.uri.to_file_path() | 10 | let path = params.text_document.file_path()?; |
10 | .map_err(|()| format_err!("invalid path"))?; | ||
11 | let file = world.file_syntax(&path)?; | 11 | let file = world.file_syntax(&path)?; |
12 | Ok(libeditor::syntax_tree(&file)) | 12 | Ok(libeditor::syntax_tree(&file)) |
13 | } | 13 | } |
14 | |||
15 | pub fn handle_extend_selection( | ||
16 | world: World, | ||
17 | params: req::ExtendSelectionParams, | ||
18 | ) -> Result<req::ExtendSelectionResult> { | ||
19 | let path = params.text_document.file_path()?; | ||
20 | let file = world.file_syntax(&path)?; | ||
21 | let line_index = world.file_line_index(&path)?; | ||
22 | let selections = params.selections.into_iter() | ||
23 | .map(|r| { | ||
24 | let r = to_text_range(&line_index, r); | ||
25 | let r = libeditor::extend_selection(&file, r).unwrap_or(r); | ||
26 | to_vs_range(&line_index, r) | ||
27 | }) | ||
28 | .collect(); | ||
29 | Ok(req::ExtendSelectionResult { selections }) | ||
30 | } | ||
31 | |||
32 | |||
33 | fn to_text_range(line_index: &LineIndex, range: Range) -> TextRange { | ||
34 | TextRange::from_to( | ||
35 | to_text_unit(line_index, range.start), | ||
36 | to_text_unit(line_index, range.end), | ||
37 | ) | ||
38 | } | ||
39 | |||
40 | fn to_text_unit(line_index: &LineIndex, position: Position) -> TextUnit { | ||
41 | // TODO: UTF-16 | ||
42 | let line_col = LineCol { | ||
43 | line: position.line as u32, | ||
44 | col: (position.character as u32).into(), | ||
45 | }; | ||
46 | line_index.offset(line_col) | ||
47 | } | ||
48 | |||
49 | |||
50 | fn to_vs_range(line_index: &LineIndex, range: TextRange) -> Range { | ||
51 | Range::new( | ||
52 | to_vs_position(line_index, range.start()), | ||
53 | to_vs_position(line_index, range.end()), | ||
54 | ) | ||
55 | } | ||
56 | |||
57 | fn to_vs_position(line_index: &LineIndex, offset: TextUnit) -> Position { | ||
58 | let line_col = line_index.line_col(offset); | ||
59 | // TODO: UTF-16 | ||
60 | Position::new(line_col.line as u64, u32::from(line_col.col) as u64) | ||
61 | } | ||
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 | |||
31 | 31 | ||
32 | use ::{ | 32 | use ::{ |
33 | io::{Io, RawMsg}, | 33 | io::{Io, RawMsg}, |
34 | handlers::handle_syntax_tree, | 34 | handlers::{handle_syntax_tree, handle_extend_selection}, |
35 | }; | 35 | }; |
36 | 36 | ||
37 | pub type Result<T> = ::std::result::Result<T, ::failure::Error>; | 37 | pub type Result<T> = ::std::result::Result<T, ::failure::Error>; |
@@ -153,34 +153,42 @@ fn main_loop( | |||
153 | 153 | ||
154 | match msg { | 154 | match msg { |
155 | RawMsg::Request(req) => { | 155 | RawMsg::Request(req) => { |
156 | let req = match dispatch::parse_request_as::<req::SyntaxTree>(req)? { | 156 | let mut req = Some(req); |
157 | Ok((params, resp)) => { | 157 | dispatch::handle_request::<req::SyntaxTree, _>(&mut req, |params, resp| { |
158 | let world = world.snapshot(); | 158 | let world = world.snapshot(); |
159 | let sender = sender.clone(); | 159 | let sender = sender.clone(); |
160 | pool.execute(move || { | 160 | pool.execute(move || { |
161 | let res: Result<String> = handle_syntax_tree(world, params); | 161 | let res = handle_syntax_tree(world, params); |
162 | sender.send(Box::new(|io: &mut Io| resp.response(io, res))) | 162 | sender.send(Box::new(|io: &mut Io| resp.response(io, res))) |
163 | }); | 163 | }); |
164 | continue; | 164 | Ok(()) |
165 | } | 165 | })?; |
166 | Err(req) => req, | 166 | dispatch::handle_request::<req::ExtendSelection, _>(&mut req, |params, resp| { |
167 | }; | 167 | let world = world.snapshot(); |
168 | 168 | let sender = sender.clone(); | |
169 | if let Some(((), resp)) = dispatch::expect_request::<req::Shutdown>(io, req)? { | 169 | pool.execute(move || { |
170 | info!("clean shutdown started"); | 170 | let res = handle_extend_selection(world, params); |
171 | sender.send(Box::new(|io: &mut Io| resp.response(io, res))) | ||
172 | }); | ||
173 | Ok(()) | ||
174 | })?; | ||
175 | dispatch::handle_request::<req::Shutdown, _>(&mut req, |(), resp| { | ||
171 | resp.result(io, ())?; | 176 | resp.result(io, ())?; |
172 | return Ok(()); | 177 | Ok(()) |
178 | })?; | ||
179 | if let Some(req) = req { | ||
180 | error!("unknown method: {:?}", req); | ||
181 | dispatch::unknown_method(io, req)?; | ||
173 | } | 182 | } |
174 | } | 183 | } |
175 | RawMsg::Notification(not) => { | 184 | RawMsg::Notification(not) => { |
176 | use dispatch::handle_notification as h; | ||
177 | let mut not = Some(not); | 185 | let mut not = Some(not); |
178 | h::<req::DidOpenTextDocument, _>(&mut not, |params| { | 186 | dispatch::handle_notification::<req::DidOpenTextDocument, _>(&mut not, |params| { |
179 | let path = params.text_document.file_path()?; | 187 | let path = params.text_document.file_path()?; |
180 | world.change_overlay(path, Some(params.text_document.text)); | 188 | world.change_overlay(path, Some(params.text_document.text)); |
181 | Ok(()) | 189 | Ok(()) |
182 | })?; | 190 | })?; |
183 | h::<req::DidChangeTextDocument, _>(&mut not, |mut params| { | 191 | dispatch::handle_notification::<req::DidChangeTextDocument, _>(&mut not, |mut params| { |
184 | let path = params.text_document.file_path()?; | 192 | let path = params.text_document.file_path()?; |
185 | let text = params.content_changes.pop() | 193 | let text = params.content_changes.pop() |
186 | .ok_or_else(|| format_err!("empty changes"))? | 194 | .ok_or_else(|| format_err!("empty changes"))? |
@@ -188,7 +196,7 @@ fn main_loop( | |||
188 | world.change_overlay(path, Some(text)); | 196 | world.change_overlay(path, Some(text)); |
189 | Ok(()) | 197 | Ok(()) |
190 | })?; | 198 | })?; |
191 | h::<req::DidCloseTextDocument, _>(&mut not, |params| { | 199 | dispatch::handle_notification::<req::DidCloseTextDocument, _>(&mut not, |params| { |
192 | let path = params.text_document.file_path()?; | 200 | let path = params.text_document.file_path()?; |
193 | world.change_overlay(path, None); | 201 | world.change_overlay(path, None); |
194 | Ok(()) | 202 | 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 { | |||
21 | 21 | ||
22 | pub enum ExtendSelection {} | 22 | pub enum ExtendSelection {} |
23 | 23 | ||
24 | impl Request for ExtendSelection { | ||
25 | type Params = ExtendSelectionParams; | ||
26 | type Result = ExtendSelectionResult; | ||
27 | const METHOD: &'static str = "m/extendSelection"; | ||
28 | } | ||
29 | |||
24 | #[derive(Deserialize, Debug)] | 30 | #[derive(Deserialize, Debug)] |
25 | #[serde(rename_all = "camelCase")] | 31 | #[serde(rename_all = "camelCase")] |
26 | pub struct ExtendSelectionParams { | 32 | pub struct ExtendSelectionParams { |
@@ -28,7 +34,8 @@ pub struct ExtendSelectionParams { | |||
28 | pub selections: Vec<Range>, | 34 | pub selections: Vec<Range>, |
29 | } | 35 | } |
30 | 36 | ||
31 | 37 | #[derive(Serialize, Debug)] | |
38 | #[serde(rename_all = "camelCase")] | ||
32 | pub struct ExtendSelectionResult { | 39 | pub struct ExtendSelectionResult { |
33 | pub selections: Vec<Range>, | 40 | pub selections: Vec<Range>, |
34 | } | 41 | } |
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 <[email protected]>"] | |||
7 | log = "0.4.2" | 7 | log = "0.4.2" |
8 | failure = "0.1.2" | 8 | failure = "0.1.2" |
9 | parking_lot = "0.6.3" | 9 | parking_lot = "0.6.3" |
10 | once_cell = "0.1.4" | ||
10 | libsyntax2 = { path = "../" } | 11 | libsyntax2 = { path = "../" } |
12 | 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 @@ | |||
1 | use parking_lot::RwLock; | ||
2 | |||
3 | const CHUNK_LEN: usize = 16; | ||
4 | |||
5 | pub struct Arena<T> { | ||
6 | chunks: RwLock<Vec<Vec<T>>>, | ||
7 | } | ||
8 | |||
9 | impl<T> Arena<T> { | ||
10 | pub fn new(&self) -> Arena<T> { | ||
11 | Arena { | ||
12 | chunks: RwLock::new(vec![Vec::with_capacity(CHUNK_LEN)]), | ||
13 | } | ||
14 | } | ||
15 | |||
16 | pub fn push(&self, value: T) -> usize { | ||
17 | let mut guard = self.chunks.write(); | ||
18 | let mut idx = (guard.len() - 1) * CHUNK_LEN; | ||
19 | let chunk = { | ||
20 | if guard.last().unwrap().len() == CHUNK_LEN { | ||
21 | guard.push(Vec::with_capacity(CHUNK_LEN)); | ||
22 | } | ||
23 | guard.last_mut().unwrap() | ||
24 | }; | ||
25 | assert!(chunk.len() < chunk.capacity()); | ||
26 | idx += chunk.len(); | ||
27 | chunk.push(value); | ||
28 | idx | ||
29 | } | ||
30 | |||
31 | pub fn get(&self, idx: usize) -> &T { | ||
32 | let chunk_idx = idx / CHUNK_LEN; | ||
33 | let chunk_off = idx - chunk_idx * CHUNK_LEN; | ||
34 | let guard = self.chunks.read(); | ||
35 | let value = &guard[chunk_idx][chunk_off]; | ||
36 | unsafe { | ||
37 | // We are careful to not move values in chunks, | ||
38 | // so this hopefully is safe | ||
39 | ::std::mem::transmute::<&T, &T>(value) | ||
40 | } | ||
41 | } | ||
42 | } | ||
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; | |||
2 | extern crate parking_lot; | 2 | extern crate parking_lot; |
3 | #[macro_use] | 3 | #[macro_use] |
4 | extern crate log; | 4 | extern crate log; |
5 | extern crate once_cell; | ||
5 | extern crate libsyntax2; | 6 | extern crate libsyntax2; |
7 | extern crate libeditor; | ||
6 | 8 | ||
7 | mod arena; | 9 | use once_cell::sync::OnceCell; |
8 | 10 | ||
9 | use std::{ | 11 | use std::{ |
10 | fs, | 12 | fs, |
@@ -14,6 +16,7 @@ use std::{ | |||
14 | }; | 16 | }; |
15 | use parking_lot::RwLock; | 17 | use parking_lot::RwLock; |
16 | use libsyntax2::ast; | 18 | use libsyntax2::ast; |
19 | use libeditor::LineIndex; | ||
17 | 20 | ||
18 | pub type Result<T> = ::std::result::Result<T, ::failure::Error>; | 21 | pub type Result<T> = ::std::result::Result<T, ::failure::Error>; |
19 | 22 | ||
@@ -39,7 +42,6 @@ impl WorldState { | |||
39 | pub fn change_overlay(&mut self, path: PathBuf, text: Option<String>) { | 42 | pub fn change_overlay(&mut self, path: PathBuf, text: Option<String>) { |
40 | let data = self.data_mut(); | 43 | let data = self.data_mut(); |
41 | data.file_map.get_mut().remove(&path); | 44 | data.file_map.get_mut().remove(&path); |
42 | data.fs_map.get_mut().remove(&path); | ||
43 | if let Some(text) = text { | 45 | if let Some(text) = text { |
44 | data.mem_map.insert(path, Arc::new(text)); | 46 | data.mem_map.insert(path, Arc::new(text)); |
45 | } else { | 47 | } else { |
@@ -49,11 +51,9 @@ impl WorldState { | |||
49 | 51 | ||
50 | fn data_mut(&mut self) -> &mut WorldData { | 52 | fn data_mut(&mut self) -> &mut WorldData { |
51 | if Arc::get_mut(&mut self.data).is_none() { | 53 | if Arc::get_mut(&mut self.data).is_none() { |
52 | let fs_map = self.data.fs_map.read().clone(); | ||
53 | let file_map = self.data.file_map.read().clone(); | 54 | let file_map = self.data.file_map.read().clone(); |
54 | self.data = Arc::new(WorldData { | 55 | self.data = Arc::new(WorldData { |
55 | mem_map: self.data.mem_map.clone(), | 56 | mem_map: self.data.mem_map.clone(), |
56 | fs_map: RwLock::new(fs_map), | ||
57 | file_map: RwLock::new(file_map), | 57 | file_map: RwLock::new(file_map), |
58 | }); | 58 | }); |
59 | } | 59 | } |
@@ -64,43 +64,57 @@ impl WorldState { | |||
64 | 64 | ||
65 | impl World { | 65 | impl World { |
66 | pub fn file_syntax(&self, path: &Path) -> Result<ast::File> { | 66 | pub fn file_syntax(&self, path: &Path) -> Result<ast::File> { |
67 | { | 67 | let data = self.file_data(path)?; |
68 | let guard = self.data.file_map.read(); | 68 | let syntax = data.syntax |
69 | if let Some(file) = guard.get(path) { | 69 | .get_or_init(|| { |
70 | return Ok(file.clone()); | 70 | trace!("parsing: {}", path.display()); |
71 | } | 71 | ast::File::parse(self.file_text(path, &data)) |
72 | } | 72 | }).clone(); |
73 | let file = self.with_file_text(path, |text| { | 73 | Ok(syntax) |
74 | trace!("parsing file: {}", path.display()); | ||
75 | ast::File::parse(text) | ||
76 | })?; | ||
77 | let mut guard = self.data.file_map.write(); | ||
78 | let file = guard.entry(path.to_owned()) | ||
79 | .or_insert(file) | ||
80 | .clone(); | ||
81 | Ok(file) | ||
82 | } | 74 | } |
83 | 75 | ||
84 | fn with_file_text<F: FnOnce(&str) -> R, R>(&self, path: &Path, f: F) -> Result<R> { | 76 | pub fn file_line_index(&self, path: &Path) -> Result<LineIndex> { |
85 | if let Some(text) = self.data.mem_map.get(path) { | 77 | let data = self.file_data(path)?; |
86 | return Ok(f(&*text)); | 78 | let index = data.lines |
79 | .get_or_init(|| { | ||
80 | trace!("calc line index: {}", path.display()); | ||
81 | LineIndex::new(self.file_text(path, &data)) | ||
82 | }); | ||
83 | Ok(index.clone()) | ||
84 | } | ||
85 | |||
86 | fn file_text<'a>(&'a self, path: &Path, file_data: &'a FileData) -> &'a str { | ||
87 | match file_data.text.as_ref() { | ||
88 | Some(text) => text.as_str(), | ||
89 | None => self.data.mem_map[path].as_str() | ||
87 | } | 90 | } |
91 | } | ||
88 | 92 | ||
93 | fn file_data(&self, path: &Path) -> Result<Arc<FileData>> { | ||
89 | { | 94 | { |
90 | let guard = self.data.fs_map.read(); | 95 | let guard = self.data.file_map.read(); |
91 | if let Some(text) = guard.get(path) { | 96 | if let Some(data) = guard.get(path) { |
92 | return Ok(f(&*text)); | 97 | return Ok(data.clone()); |
93 | } | 98 | } |
94 | } | 99 | } |
95 | trace!("loading file from disk: {}", path.display()); | 100 | |
96 | let text = fs::read_to_string(path)?; | 101 | let text = if self.data.mem_map.contains_key(path) { |
97 | { | 102 | None |
98 | let mut guard = self.data.fs_map.write(); | 103 | } else { |
104 | trace!("loading file from disk: {}", path.display()); | ||
105 | Some(fs::read_to_string(path)?) | ||
106 | }; | ||
107 | let res = { | ||
108 | let mut guard = self.data.file_map.write(); | ||
99 | guard.entry(path.to_owned()) | 109 | guard.entry(path.to_owned()) |
100 | .or_insert_with(|| Arc::new(text)); | 110 | .or_insert_with(|| Arc::new(FileData { |
101 | } | 111 | text, |
102 | let guard = self.data.fs_map.read(); | 112 | syntax: OnceCell::new(), |
103 | Ok(f(&guard[path])) | 113 | lines: OnceCell::new(), |
114 | })) | ||
115 | .clone() | ||
116 | }; | ||
117 | Ok(res) | ||
104 | } | 118 | } |
105 | } | 119 | } |
106 | 120 | ||
@@ -108,6 +122,11 @@ impl World { | |||
108 | #[derive(Default)] | 122 | #[derive(Default)] |
109 | struct WorldData { | 123 | struct WorldData { |
110 | mem_map: HashMap<PathBuf, Arc<String>>, | 124 | mem_map: HashMap<PathBuf, Arc<String>>, |
111 | fs_map: RwLock<HashMap<PathBuf, Arc<String>>>, | 125 | file_map: RwLock<HashMap<PathBuf, Arc<FileData>>>, |
112 | file_map: RwLock<HashMap<PathBuf, ast::File>>, | 126 | } |
127 | |||
128 | struct FileData { | ||
129 | text: Option<String>, | ||
130 | syntax: OnceCell<ast::File>, | ||
131 | lines: OnceCell<LineIndex>, | ||
113 | } | 132 | } |
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::{ | |||
10 | SyntaxKind::*, | 10 | SyntaxKind::*, |
11 | }; | 11 | }; |
12 | pub use libsyntax2::{TextRange, TextUnit, ast}; | 12 | pub use libsyntax2::{TextRange, TextUnit, ast}; |
13 | pub use self::line_index::{LineIndex, LineCol}; | ||
13 | 14 | ||
14 | #[derive(Debug)] | 15 | #[derive(Debug)] |
15 | pub struct HighlightedRange { | 16 | 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 @@ | |||
1 | use superslice::Ext; | 1 | use superslice::Ext; |
2 | use ::{TextUnit}; | 2 | use ::TextUnit; |
3 | 3 | ||
4 | #[derive(Clone, Debug)] | ||
4 | pub struct LineIndex { | 5 | pub struct LineIndex { |
5 | newlines: Vec<TextUnit>, | 6 | newlines: Vec<TextUnit>, |
6 | } | 7 | } |
@@ -24,11 +25,16 @@ impl LineIndex { | |||
24 | LineIndex { newlines } | 25 | LineIndex { newlines } |
25 | } | 26 | } |
26 | 27 | ||
27 | pub fn translate(&self, offset: TextUnit) -> LineCol { | 28 | pub fn line_col(&self, offset: TextUnit) -> LineCol { |
28 | let line = self.newlines.upper_bound(&offset) - 1; | 29 | let line = self.newlines.upper_bound(&offset) - 1; |
29 | let line_start_offset = self.newlines[line]; | 30 | let line_start_offset = self.newlines[line]; |
30 | let col = offset - line_start_offset; | 31 | let col = offset - line_start_offset; |
31 | return LineCol { line: line as u32, col } | 32 | return LineCol { line: line as u32, col }; |
33 | } | ||
34 | |||
35 | pub fn offset(&self, line_col: LineCol) -> TextUnit { | ||
36 | //TODO: return Result | ||
37 | self.newlines[line_col.line as usize] + line_col.col | ||
32 | } | 38 | } |
33 | } | 39 | } |
34 | 40 | ||
@@ -36,21 +42,21 @@ impl LineIndex { | |||
36 | fn test_line_index() { | 42 | fn test_line_index() { |
37 | let text = "hello\nworld"; | 43 | let text = "hello\nworld"; |
38 | let index = LineIndex::new(text); | 44 | let index = LineIndex::new(text); |
39 | assert_eq!(index.translate(0.into()), LineCol { line: 0, col: 0.into()}); | 45 | assert_eq!(index.line_col(0.into()), LineCol { line: 0, col: 0.into() }); |
40 | assert_eq!(index.translate(1.into()), LineCol { line: 0, col: 1.into()}); | 46 | assert_eq!(index.line_col(1.into()), LineCol { line: 0, col: 1.into() }); |
41 | assert_eq!(index.translate(5.into()), LineCol { line: 0, col: 5.into()}); | 47 | assert_eq!(index.line_col(5.into()), LineCol { line: 0, col: 5.into() }); |
42 | assert_eq!(index.translate(6.into()), LineCol { line: 1, col: 0.into()}); | 48 | assert_eq!(index.line_col(6.into()), LineCol { line: 1, col: 0.into() }); |
43 | assert_eq!(index.translate(7.into()), LineCol { line: 1, col: 1.into()}); | 49 | assert_eq!(index.line_col(7.into()), LineCol { line: 1, col: 1.into() }); |
44 | assert_eq!(index.translate(8.into()), LineCol { line: 1, col: 2.into()}); | 50 | assert_eq!(index.line_col(8.into()), LineCol { line: 1, col: 2.into() }); |
45 | assert_eq!(index.translate(10.into()), LineCol { line: 1, col: 4.into()}); | 51 | assert_eq!(index.line_col(10.into()), LineCol { line: 1, col: 4.into() }); |
46 | assert_eq!(index.translate(11.into()), LineCol { line: 1, col: 5.into()}); | 52 | assert_eq!(index.line_col(11.into()), LineCol { line: 1, col: 5.into() }); |
47 | assert_eq!(index.translate(12.into()), LineCol { line: 1, col: 6.into()}); | 53 | assert_eq!(index.line_col(12.into()), LineCol { line: 1, col: 6.into() }); |
48 | 54 | ||
49 | let text = "\nhello\nworld"; | 55 | let text = "\nhello\nworld"; |
50 | let index = LineIndex::new(text); | 56 | let index = LineIndex::new(text); |
51 | assert_eq!(index.translate(0.into()), LineCol { line: 0, col: 0.into()}); | 57 | assert_eq!(index.line_col(0.into()), LineCol { line: 0, col: 0.into() }); |
52 | assert_eq!(index.translate(1.into()), LineCol { line: 1, col: 0.into()}); | 58 | assert_eq!(index.line_col(1.into()), LineCol { line: 1, col: 0.into() }); |
53 | assert_eq!(index.translate(2.into()), LineCol { line: 1, col: 1.into()}); | 59 | assert_eq!(index.line_col(2.into()), LineCol { line: 1, col: 1.into() }); |
54 | assert_eq!(index.translate(6.into()), LineCol { line: 1, col: 5.into()}); | 60 | assert_eq!(index.line_col(6.into()), LineCol { line: 1, col: 5.into() }); |
55 | assert_eq!(index.translate(7.into()), LineCol { line: 2, col: 0.into()}); | 61 | assert_eq!(index.line_col(7.into()), LineCol { line: 2, col: 0.into() }); |
56 | } | 62 | } |