diff options
author | Aleksey Kladov <[email protected]> | 2018-08-10 19:13:39 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2018-08-10 19:13:39 +0100 |
commit | 120789804d5483f14c9682b2b777adf6d2992547 (patch) | |
tree | bccbe4a72e648516f838499b91e5158484b5cbcd /codeless | |
parent | 1be7af26a83b79863efb0d66a77b1fb7c0235bd2 (diff) |
Add line index
Diffstat (limited to 'codeless')
-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 |
6 files changed, 198 insertions, 28 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 | } | ||