diff options
-rw-r--r-- | codeless/server/src/caps.rs | 17 | ||||
-rw-r--r-- | codeless/server/src/dispatch.rs | 41 | ||||
-rw-r--r-- | codeless/server/src/handlers.rs | 13 | ||||
-rw-r--r-- | codeless/server/src/main.rs | 75 | ||||
-rw-r--r-- | codeless/server/src/req.rs | 25 | ||||
-rw-r--r-- | codeless/src/extension.ts | 55 | ||||
-rw-r--r-- | libanalysis/Cargo.toml | 1 | ||||
-rw-r--r-- | libanalysis/src/lib.rs | 14 | ||||
-rw-r--r-- | libeditor/Cargo.toml | 3 | ||||
-rw-r--r-- | libeditor/src/lib.rs | 2 | ||||
-rw-r--r-- | libeditor/src/line_index.rs | 56 |
11 files changed, 269 insertions, 33 deletions
diff --git a/codeless/server/src/caps.rs b/codeless/server/src/caps.rs index b2fad6732..3d89c64a9 100644 --- a/codeless/server/src/caps.rs +++ b/codeless/server/src/caps.rs | |||
@@ -1,7 +1,20 @@ | |||
1 | use languageserver_types::ServerCapabilities; | 1 | use languageserver_types::{ |
2 | ServerCapabilities, | ||
3 | TextDocumentSyncCapability, | ||
4 | TextDocumentSyncOptions, | ||
5 | TextDocumentSyncKind, | ||
6 | }; | ||
2 | 7 | ||
3 | pub const SERVER_CAPABILITIES: ServerCapabilities = ServerCapabilities { | 8 | pub const SERVER_CAPABILITIES: ServerCapabilities = ServerCapabilities { |
4 | text_document_sync: None, | 9 | text_document_sync: Some(TextDocumentSyncCapability::Options( |
10 | TextDocumentSyncOptions { | ||
11 | open_close: Some(true), | ||
12 | change: Some(TextDocumentSyncKind::Full), | ||
13 | will_save: None, | ||
14 | will_save_wait_until: None, | ||
15 | save: None, | ||
16 | } | ||
17 | )), | ||
5 | hover_provider: None, | 18 | hover_provider: None, |
6 | completion_provider: None, | 19 | completion_provider: None, |
7 | signature_help_provider: None, | 20 | signature_help_provider: None, |
diff --git a/codeless/server/src/dispatch.rs b/codeless/server/src/dispatch.rs index ee87fa6c3..41437b62a 100644 --- a/codeless/server/src/dispatch.rs +++ b/codeless/server/src/dispatch.rs | |||
@@ -9,8 +9,8 @@ use drop_bomb::DropBomb; | |||
9 | 9 | ||
10 | use ::{ | 10 | use ::{ |
11 | Result, | 11 | Result, |
12 | req::Request, | 12 | req::{Request, Notification}, |
13 | io::{Io, RawMsg, RawResponse, RawRequest}, | 13 | io::{Io, RawMsg, RawResponse, RawRequest, RawNotification}, |
14 | }; | 14 | }; |
15 | 15 | ||
16 | pub struct Responder<R: Request> { | 16 | pub struct Responder<R: Request> { |
@@ -52,7 +52,7 @@ impl<R: Request> Responder<R> | |||
52 | } | 52 | } |
53 | 53 | ||
54 | 54 | ||
55 | pub fn parse_as<R>(raw: RawRequest) -> Result<::std::result::Result<(R::Params, Responder<R>), RawRequest>> | 55 | pub 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,13 +71,13 @@ pub fn parse_as<R>(raw: RawRequest) -> Result<::std::result::Result<(R::Params, | |||
71 | Ok(Ok((params, responder))) | 71 | Ok(Ok((params, responder))) |
72 | } | 72 | } |
73 | 73 | ||
74 | pub fn expect<R>(io: &mut Io, raw: RawRequest) -> Result<Option<(R::Params, Responder<R>)>> | 74 | pub fn expect_request<R>(io: &mut Io, raw: RawRequest) -> Result<Option<(R::Params, Responder<R>)>> |
75 | where | 75 | where |
76 | R: Request, | 76 | R: Request, |
77 | R::Params: DeserializeOwned, | 77 | R::Params: DeserializeOwned, |
78 | R::Result: Serialize, | 78 | R::Result: Serialize, |
79 | { | 79 | { |
80 | let ret = match parse_as::<R>(raw)? { | 80 | let ret = match parse_request_as::<R>(raw)? { |
81 | Ok(x) => Some(x), | 81 | Ok(x) => Some(x), |
82 | Err(raw) => { | 82 | Err(raw) => { |
83 | unknown_method(io, raw)?; | 83 | unknown_method(io, raw)?; |
@@ -87,6 +87,37 @@ pub fn expect<R>(io: &mut Io, raw: RawRequest) -> Result<Option<(R::Params, Resp | |||
87 | Ok(ret) | 87 | Ok(ret) |
88 | } | 88 | } |
89 | 89 | ||
90 | pub fn parse_notification_as<N>(raw: RawNotification) -> Result<::std::result::Result<N::Params, RawNotification>> | ||
91 | where | ||
92 | N: Notification, | ||
93 | N::Params: DeserializeOwned, | ||
94 | { | ||
95 | if raw.method != N::METHOD { | ||
96 | return Ok(Err(raw)); | ||
97 | } | ||
98 | let params: N::Params = serde_json::from_value(raw.params)?; | ||
99 | Ok(Ok(params)) | ||
100 | } | ||
101 | |||
102 | pub fn handle_notification<N, F>(not: &mut Option<RawNotification>, f: F) -> Result<()> | ||
103 | where | ||
104 | N: Notification, | ||
105 | N::Params: DeserializeOwned, | ||
106 | F: FnOnce(N::Params) -> Result<()> | ||
107 | { | ||
108 | match not.take() { | ||
109 | None => Ok(()), | ||
110 | Some(n) => match parse_notification_as::<N>(n)? { | ||
111 | Ok(params) => f(params), | ||
112 | Err(n) => { | ||
113 | *not = Some(n); | ||
114 | Ok(()) | ||
115 | }, | ||
116 | } | ||
117 | } | ||
118 | } | ||
119 | |||
120 | |||
90 | pub fn unknown_method(io: &mut Io, raw: RawRequest) -> Result<()> { | 121 | pub fn unknown_method(io: &mut Io, raw: RawRequest) -> Result<()> { |
91 | error(io, raw.id, ErrorCode::MethodNotFound, "unknown method") | 122 | error(io, raw.id, ErrorCode::MethodNotFound, "unknown method") |
92 | } | 123 | } |
diff --git a/codeless/server/src/handlers.rs b/codeless/server/src/handlers.rs new file mode 100644 index 000000000..3f257941a --- /dev/null +++ b/codeless/server/src/handlers.rs | |||
@@ -0,0 +1,13 @@ | |||
1 | use libanalysis::World; | ||
2 | use libeditor; | ||
3 | use {req, Result}; | ||
4 | |||
5 | pub fn handle_syntax_tree( | ||
6 | world: World, | ||
7 | params: req::SyntaxTreeParams | ||
8 | ) -> Result<String> { | ||
9 | let path = params.text_document.uri.to_file_path() | ||
10 | .map_err(|()| format_err!("invalid path"))?; | ||
11 | let file = world.file_syntax(&path)?; | ||
12 | Ok(libeditor::syntax_tree(&file)) | ||
13 | } | ||
diff --git a/codeless/server/src/main.rs b/codeless/server/src/main.rs index fdb2fe2d5..287d650fa 100644 --- a/codeless/server/src/main.rs +++ b/codeless/server/src/main.rs | |||
@@ -19,20 +19,25 @@ mod io; | |||
19 | mod caps; | 19 | mod caps; |
20 | mod req; | 20 | mod req; |
21 | mod dispatch; | 21 | mod dispatch; |
22 | mod handlers; | ||
23 | |||
24 | use std::path::PathBuf; | ||
22 | 25 | ||
23 | use threadpool::ThreadPool; | 26 | use threadpool::ThreadPool; |
24 | use crossbeam_channel::{bounded, Sender, Receiver}; | 27 | use crossbeam_channel::{bounded, Sender, Receiver}; |
25 | use flexi_logger::Logger; | 28 | use flexi_logger::Logger; |
26 | use libanalysis::WorldState; | 29 | use libanalysis::WorldState; |
30 | use languageserver_types::{TextDocumentItem, VersionedTextDocumentIdentifier, TextDocumentIdentifier}; | ||
27 | 31 | ||
28 | use ::{ | 32 | use ::{ |
29 | io::{Io, RawMsg}, | 33 | io::{Io, RawMsg}, |
34 | handlers::handle_syntax_tree, | ||
30 | }; | 35 | }; |
31 | 36 | ||
32 | pub type Result<T> = ::std::result::Result<T, ::failure::Error>; | 37 | pub type Result<T> = ::std::result::Result<T, ::failure::Error>; |
33 | 38 | ||
34 | fn main() -> Result<()> { | 39 | fn main() -> Result<()> { |
35 | Logger::with_env_or_str("m=trace") | 40 | Logger::with_env_or_str("m=trace, libanalysis=trace") |
36 | .log_to_file() | 41 | .log_to_file() |
37 | .directory("log") | 42 | .directory("log") |
38 | .start()?; | 43 | .start()?; |
@@ -70,7 +75,7 @@ fn initialize(io: &mut Io) -> Result<()> { | |||
70 | loop { | 75 | loop { |
71 | match io.recv()? { | 76 | match io.recv()? { |
72 | RawMsg::Request(req) => { | 77 | RawMsg::Request(req) => { |
73 | if let Some((_params, resp)) = dispatch::expect::<req::Initialize>(io, req)? { | 78 | if let Some((_params, resp)) = dispatch::expect_request::<req::Initialize>(io, req)? { |
74 | resp.result(io, req::InitializeResult { | 79 | resp.result(io, req::InitializeResult { |
75 | capabilities: caps::SERVER_CAPABILITIES | 80 | capabilities: caps::SERVER_CAPABILITIES |
76 | })?; | 81 | })?; |
@@ -148,18 +153,12 @@ fn main_loop( | |||
148 | 153 | ||
149 | match msg { | 154 | match msg { |
150 | RawMsg::Request(req) => { | 155 | RawMsg::Request(req) => { |
151 | let req = match dispatch::parse_as::<req::SyntaxTree>(req)? { | 156 | let req = match dispatch::parse_request_as::<req::SyntaxTree>(req)? { |
152 | Ok((params, resp)) => { | 157 | Ok((params, resp)) => { |
153 | let world = world.snapshot(); | 158 | let world = world.snapshot(); |
154 | let sender = sender.clone(); | 159 | let sender = sender.clone(); |
155 | pool.execute(move || { | 160 | pool.execute(move || { |
156 | let res: Result<String> = (|| { | 161 | let res: Result<String> = handle_syntax_tree(world, params); |
157 | let path = params.text_document.uri.to_file_path() | ||
158 | .map_err(|()| format_err!("invalid path"))?; | ||
159 | let file = world.file_syntax(&path)?; | ||
160 | Ok(libeditor::syntax_tree(&file)) | ||
161 | })(); | ||
162 | |||
163 | sender.send(Box::new(|io: &mut Io| resp.response(io, res))) | 162 | sender.send(Box::new(|io: &mut Io| resp.response(io, res))) |
164 | }); | 163 | }); |
165 | continue; | 164 | continue; |
@@ -167,12 +166,38 @@ fn main_loop( | |||
167 | Err(req) => req, | 166 | Err(req) => req, |
168 | }; | 167 | }; |
169 | 168 | ||
170 | if let Some(((), resp)) = dispatch::expect::<req::Shutdown>(io, req)? { | 169 | if let Some(((), resp)) = dispatch::expect_request::<req::Shutdown>(io, req)? { |
171 | info!("shutdown request"); | 170 | info!("clean shutdown started"); |
172 | resp.result(io, ())?; | 171 | resp.result(io, ())?; |
173 | return Ok(()); | 172 | return Ok(()); |
174 | } | 173 | } |
175 | } | 174 | } |
175 | RawMsg::Notification(not) => { | ||
176 | use dispatch::handle_notification as h; | ||
177 | let mut not = Some(not); | ||
178 | h::<req::DidOpenTextDocument, _>(&mut not, |params| { | ||
179 | let path = params.text_document.file_path()?; | ||
180 | world.change_overlay(path, Some(params.text_document.text)); | ||
181 | Ok(()) | ||
182 | })?; | ||
183 | h::<req::DidChangeTextDocument, _>(&mut not, |mut params| { | ||
184 | let path = params.text_document.file_path()?; | ||
185 | let text = params.content_changes.pop() | ||
186 | .ok_or_else(|| format_err!("empty changes"))? | ||
187 | .text; | ||
188 | world.change_overlay(path, Some(text)); | ||
189 | Ok(()) | ||
190 | })?; | ||
191 | h::<req::DidCloseTextDocument, _>(&mut not, |params| { | ||
192 | let path = params.text_document.file_path()?; | ||
193 | world.change_overlay(path, None); | ||
194 | Ok(()) | ||
195 | })?; | ||
196 | |||
197 | if let Some(not) = not { | ||
198 | error!("unhandled notification: {:?}", not) | ||
199 | } | ||
200 | } | ||
176 | msg => { | 201 | msg => { |
177 | eprintln!("msg = {:?}", msg); | 202 | eprintln!("msg = {:?}", msg); |
178 | } | 203 | } |
@@ -180,7 +205,6 @@ fn main_loop( | |||
180 | } | 205 | } |
181 | } | 206 | } |
182 | 207 | ||
183 | |||
184 | trait FnBox<A, R>: Send { | 208 | trait FnBox<A, R>: Send { |
185 | fn call_box(self: Box<Self>, a: A) -> R; | 209 | fn call_box(self: Box<Self>, a: A) -> R; |
186 | } | 210 | } |
@@ -190,3 +214,28 @@ impl<A, R, F: FnOnce(A) -> R + Send> FnBox<A, R> for F { | |||
190 | (*self)(a) | 214 | (*self)(a) |
191 | } | 215 | } |
192 | } | 216 | } |
217 | |||
218 | trait FilePath { | ||
219 | fn file_path(&self) -> Result<PathBuf>; | ||
220 | } | ||
221 | |||
222 | impl FilePath for TextDocumentItem { | ||
223 | fn file_path(&self) -> Result<PathBuf> { | ||
224 | self.uri.to_file_path() | ||
225 | .map_err(|()| format_err!("invalid uri: {}", self.uri)) | ||
226 | } | ||
227 | } | ||
228 | |||
229 | impl FilePath for VersionedTextDocumentIdentifier { | ||
230 | fn file_path(&self) -> Result<PathBuf> { | ||
231 | self.uri.to_file_path() | ||
232 | .map_err(|()| format_err!("invalid uri: {}", self.uri)) | ||
233 | } | ||
234 | } | ||
235 | |||
236 | impl FilePath for TextDocumentIdentifier { | ||
237 | fn file_path(&self) -> Result<PathBuf> { | ||
238 | self.uri.to_file_path() | ||
239 | .map_err(|()| format_err!("invalid uri: {}", self.uri)) | ||
240 | } | ||
241 | } | ||
diff --git a/codeless/server/src/req.rs b/codeless/server/src/req.rs index 35a20a229..ee4a786c7 100644 --- a/codeless/server/src/req.rs +++ b/codeless/server/src/req.rs | |||
@@ -1,6 +1,9 @@ | |||
1 | use languageserver_types::TextDocumentIdentifier; | 1 | use languageserver_types::{TextDocumentIdentifier, Range}; |
2 | pub use languageserver_types::request::*; | 2 | |
3 | pub use languageserver_types::{InitializeResult}; | 3 | pub use languageserver_types::{ |
4 | request::*, notification::*, | ||
5 | InitializeResult, | ||
6 | }; | ||
4 | 7 | ||
5 | pub enum SyntaxTree {} | 8 | pub enum SyntaxTree {} |
6 | 9 | ||
@@ -11,7 +14,21 @@ impl Request for SyntaxTree { | |||
11 | } | 14 | } |
12 | 15 | ||
13 | #[derive(Deserialize, Debug)] | 16 | #[derive(Deserialize, Debug)] |
14 | #[serde(rename_all="camelCase")] | 17 | #[serde(rename_all = "camelCase")] |
15 | pub struct SyntaxTreeParams { | 18 | pub struct SyntaxTreeParams { |
16 | pub text_document: TextDocumentIdentifier | 19 | pub text_document: TextDocumentIdentifier |
17 | } | 20 | } |
21 | |||
22 | pub enum ExtendSelection {} | ||
23 | |||
24 | #[derive(Deserialize, Debug)] | ||
25 | #[serde(rename_all = "camelCase")] | ||
26 | pub struct ExtendSelectionParams { | ||
27 | pub text_document: TextDocumentIdentifier, | ||
28 | pub selections: Vec<Range>, | ||
29 | } | ||
30 | |||
31 | |||
32 | pub struct ExtendSelectionResult { | ||
33 | pub selections: Vec<Range>, | ||
34 | } | ||
diff --git a/codeless/src/extension.ts b/codeless/src/extension.ts index c64065e6b..792af5a73 100644 --- a/codeless/src/extension.ts +++ b/codeless/src/extension.ts | |||
@@ -6,7 +6,8 @@ import { | |||
6 | ServerOptions, | 6 | ServerOptions, |
7 | TransportKind, | 7 | TransportKind, |
8 | Executable, | 8 | Executable, |
9 | TextDocumentIdentifier | 9 | TextDocumentIdentifier, |
10 | Range | ||
10 | } from 'vscode-languageclient'; | 11 | } from 'vscode-languageclient'; |
11 | 12 | ||
12 | 13 | ||
@@ -18,6 +19,7 @@ let uris = { | |||
18 | 19 | ||
19 | 20 | ||
20 | export function activate(context: vscode.ExtensionContext) { | 21 | export function activate(context: vscode.ExtensionContext) { |
22 | let textDocumentContentProvider = new TextDocumentContentProvider() | ||
21 | let dispose = (disposable) => { | 23 | let dispose = (disposable) => { |
22 | context.subscriptions.push(disposable); | 24 | context.subscriptions.push(disposable); |
23 | } | 25 | } |
@@ -26,11 +28,39 @@ export function activate(context: vscode.ExtensionContext) { | |||
26 | } | 28 | } |
27 | 29 | ||
28 | registerCommand('libsyntax-rust.syntaxTree', () => openDoc(uris.syntaxTree)) | 30 | registerCommand('libsyntax-rust.syntaxTree', () => openDoc(uris.syntaxTree)) |
31 | registerCommand('libsyntax-rust.extendSelection', async () => { | ||
32 | let editor = vscode.window.activeTextEditor | ||
33 | if (editor == null || editor.document.languageId != "rust") return | ||
34 | let request: ExtendSelectionParams = { | ||
35 | textDocument: { uri: editor.document.uri.toString() }, | ||
36 | selections: editor.selections.map((s) => { | ||
37 | let r: Range = { start: s.start, end: s.end } | ||
38 | return r; | ||
39 | }) | ||
40 | } | ||
41 | let response = await client.sendRequest<ExtendSelectionResult>("m/extendSelection", request) | ||
42 | editor.selections = response.selections.map((range) => { | ||
43 | return new vscode.Selection( | ||
44 | new vscode.Position(range.start.line, range.start.character), | ||
45 | new vscode.Position(range.end.line, range.end.character), | ||
46 | ) | ||
47 | }) | ||
48 | }) | ||
49 | |||
29 | dispose(vscode.workspace.registerTextDocumentContentProvider( | 50 | dispose(vscode.workspace.registerTextDocumentContentProvider( |
30 | 'libsyntax-rust', | 51 | 'libsyntax-rust', |
31 | new TextDocumentContentProvider() | 52 | textDocumentContentProvider |
32 | )) | 53 | )) |
33 | startServer() | 54 | startServer() |
55 | vscode.workspace.onDidChangeTextDocument((event: vscode.TextDocumentChangeEvent) => { | ||
56 | let doc = event.document | ||
57 | if (doc.languageId != "rust") return | ||
58 | // We need to order this after LS updates, but there's no API for that. | ||
59 | // Hence, good old setTimeout. | ||
60 | setTimeout(() => { | ||
61 | textDocumentContentProvider.eventEmitter.fire(uris.syntaxTree) | ||
62 | }, 10) | ||
63 | }, null, context.subscriptions) | ||
34 | } | 64 | } |
35 | 65 | ||
36 | export function deactivate(): Thenable<void> { | 66 | export function deactivate(): Thenable<void> { |
@@ -76,11 +106,28 @@ class TextDocumentContentProvider implements vscode.TextDocumentContentProvider | |||
76 | public provideTextDocumentContent(uri: vscode.Uri): vscode.ProviderResult<string> { | 106 | public provideTextDocumentContent(uri: vscode.Uri): vscode.ProviderResult<string> { |
77 | let editor = vscode.window.activeTextEditor; | 107 | let editor = vscode.window.activeTextEditor; |
78 | if (editor == null) return "" | 108 | if (editor == null) return "" |
79 | let textDocument: TextDocumentIdentifier = { uri: editor.document.uri.toString() }; | 109 | let request: SyntaxTreeParams = { |
80 | return client.sendRequest("m/syntaxTree", { textDocument }) | 110 | textDocument: { uri: editor.document.uri.toString() } |
111 | }; | ||
112 | return client.sendRequest<SyntaxTreeResult>("m/syntaxTree", request); | ||
81 | } | 113 | } |
82 | 114 | ||
83 | get onDidChange(): vscode.Event<vscode.Uri> { | 115 | get onDidChange(): vscode.Event<vscode.Uri> { |
84 | return this.eventEmitter.event | 116 | return this.eventEmitter.event |
85 | } | 117 | } |
86 | } | 118 | } |
119 | |||
120 | interface SyntaxTreeParams { | ||
121 | textDocument: TextDocumentIdentifier; | ||
122 | } | ||
123 | |||
124 | type SyntaxTreeResult = string | ||
125 | |||
126 | interface ExtendSelectionParams { | ||
127 | textDocument: TextDocumentIdentifier; | ||
128 | selections: Range[]; | ||
129 | } | ||
130 | |||
131 | interface ExtendSelectionResult { | ||
132 | selections: Range[]; | ||
133 | } | ||
diff --git a/libanalysis/Cargo.toml b/libanalysis/Cargo.toml index bde5043e8..737463258 100644 --- a/libanalysis/Cargo.toml +++ b/libanalysis/Cargo.toml | |||
@@ -4,6 +4,7 @@ version = "0.1.0" | |||
4 | authors = ["Aleksey Kladov <[email protected]>"] | 4 | authors = ["Aleksey Kladov <[email protected]>"] |
5 | 5 | ||
6 | [dependencies] | 6 | [dependencies] |
7 | log = "0.4.2" | ||
7 | failure = "0.1.2" | 8 | failure = "0.1.2" |
8 | parking_lot = "0.6.3" | 9 | parking_lot = "0.6.3" |
9 | libsyntax2 = { path = "../" } | 10 | libsyntax2 = { path = "../" } |
diff --git a/libanalysis/src/lib.rs b/libanalysis/src/lib.rs index 07e7d567d..417a544ca 100644 --- a/libanalysis/src/lib.rs +++ b/libanalysis/src/lib.rs | |||
@@ -1,6 +1,10 @@ | |||
1 | extern crate failure; | 1 | extern crate failure; |
2 | extern crate libsyntax2; | ||
3 | extern crate parking_lot; | 2 | extern crate parking_lot; |
3 | #[macro_use] | ||
4 | extern crate log; | ||
5 | extern crate libsyntax2; | ||
6 | |||
7 | mod arena; | ||
4 | 8 | ||
5 | use std::{ | 9 | use std::{ |
6 | fs, | 10 | fs, |
@@ -66,8 +70,10 @@ impl World { | |||
66 | return Ok(file.clone()); | 70 | return Ok(file.clone()); |
67 | } | 71 | } |
68 | } | 72 | } |
69 | 73 | let file = self.with_file_text(path, |text| { | |
70 | let file = self.with_file_text(path, ast::File::parse)?; | 74 | trace!("parsing file: {}", path.display()); |
75 | ast::File::parse(text) | ||
76 | })?; | ||
71 | let mut guard = self.data.file_map.write(); | 77 | let mut guard = self.data.file_map.write(); |
72 | let file = guard.entry(path.to_owned()) | 78 | let file = guard.entry(path.to_owned()) |
73 | .or_insert(file) | 79 | .or_insert(file) |
@@ -86,7 +92,7 @@ impl World { | |||
86 | return Ok(f(&*text)); | 92 | return Ok(f(&*text)); |
87 | } | 93 | } |
88 | } | 94 | } |
89 | 95 | trace!("loading file from disk: {}", path.display()); | |
90 | let text = fs::read_to_string(path)?; | 96 | let text = fs::read_to_string(path)?; |
91 | { | 97 | { |
92 | let mut guard = self.data.fs_map.write(); | 98 | let mut guard = self.data.fs_map.write(); |
diff --git a/libeditor/Cargo.toml b/libeditor/Cargo.toml index 750d80038..fedcf790a 100644 --- a/libeditor/Cargo.toml +++ b/libeditor/Cargo.toml | |||
@@ -5,5 +5,6 @@ authors = ["Aleksey Kladov <[email protected]>"] | |||
5 | publish = false | 5 | publish = false |
6 | 6 | ||
7 | [dependencies] | 7 | [dependencies] |
8 | libsyntax2 = { path = "../" } | ||
9 | itertools = "0.7.8" | 8 | itertools = "0.7.8" |
9 | superslice = "0.1.0" | ||
10 | libsyntax2 = { path = "../" } | ||
diff --git a/libeditor/src/lib.rs b/libeditor/src/lib.rs index 4e9631a8b..9da71743f 100644 --- a/libeditor/src/lib.rs +++ b/libeditor/src/lib.rs | |||
@@ -1,6 +1,8 @@ | |||
1 | extern crate libsyntax2; | 1 | extern crate libsyntax2; |
2 | extern crate superslice; | ||
2 | 3 | ||
3 | mod extend_selection; | 4 | mod extend_selection; |
5 | mod line_index; | ||
4 | 6 | ||
5 | use libsyntax2::{ | 7 | use libsyntax2::{ |
6 | SyntaxNodeRef, AstNode, | 8 | SyntaxNodeRef, AstNode, |
diff --git a/libeditor/src/line_index.rs b/libeditor/src/line_index.rs new file mode 100644 index 000000000..feb482b32 --- /dev/null +++ b/libeditor/src/line_index.rs | |||
@@ -0,0 +1,56 @@ | |||
1 | use superslice::Ext; | ||
2 | use ::{TextUnit}; | ||
3 | |||
4 | pub struct LineIndex { | ||
5 | newlines: Vec<TextUnit>, | ||
6 | } | ||
7 | |||
8 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
9 | pub struct LineCol { | ||
10 | pub line: u32, | ||
11 | pub col: TextUnit, | ||
12 | } | ||
13 | |||
14 | impl LineIndex { | ||
15 | pub fn new(text: &str) -> LineIndex { | ||
16 | let mut newlines = vec![0.into()]; | ||
17 | let mut curr = 0.into(); | ||
18 | for c in text.chars() { | ||
19 | curr += TextUnit::of_char(c); | ||
20 | if c == '\n' { | ||
21 | newlines.push(curr); | ||
22 | } | ||
23 | } | ||
24 | LineIndex { newlines } | ||
25 | } | ||
26 | |||
27 | pub fn translate(&self, offset: TextUnit) -> LineCol { | ||
28 | let line = self.newlines.upper_bound(&offset) - 1; | ||
29 | let line_start_offset = self.newlines[line]; | ||
30 | let col = offset - line_start_offset; | ||
31 | return LineCol { line: line as u32, col } | ||
32 | } | ||
33 | } | ||
34 | |||
35 | #[test] | ||
36 | fn test_line_index() { | ||
37 | let text = "hello\nworld"; | ||
38 | let index = LineIndex::new(text); | ||
39 | assert_eq!(index.translate(0.into()), LineCol { line: 0, col: 0.into()}); | ||
40 | assert_eq!(index.translate(1.into()), LineCol { line: 0, col: 1.into()}); | ||
41 | assert_eq!(index.translate(5.into()), LineCol { line: 0, col: 5.into()}); | ||
42 | assert_eq!(index.translate(6.into()), LineCol { line: 1, col: 0.into()}); | ||
43 | assert_eq!(index.translate(7.into()), LineCol { line: 1, col: 1.into()}); | ||
44 | assert_eq!(index.translate(8.into()), LineCol { line: 1, col: 2.into()}); | ||
45 | assert_eq!(index.translate(10.into()), LineCol { line: 1, col: 4.into()}); | ||
46 | assert_eq!(index.translate(11.into()), LineCol { line: 1, col: 5.into()}); | ||
47 | assert_eq!(index.translate(12.into()), LineCol { line: 1, col: 6.into()}); | ||
48 | |||
49 | let text = "\nhello\nworld"; | ||
50 | let index = LineIndex::new(text); | ||
51 | assert_eq!(index.translate(0.into()), LineCol { line: 0, col: 0.into()}); | ||
52 | assert_eq!(index.translate(1.into()), LineCol { line: 1, col: 0.into()}); | ||
53 | assert_eq!(index.translate(2.into()), LineCol { line: 1, col: 1.into()}); | ||
54 | assert_eq!(index.translate(6.into()), LineCol { line: 1, col: 5.into()}); | ||
55 | assert_eq!(index.translate(7.into()), LineCol { line: 2, col: 0.into()}); | ||
56 | } | ||