aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--codeless/server/src/caps.rs17
-rw-r--r--codeless/server/src/dispatch.rs41
-rw-r--r--codeless/server/src/handlers.rs13
-rw-r--r--codeless/server/src/main.rs75
-rw-r--r--codeless/server/src/req.rs25
-rw-r--r--codeless/src/extension.ts55
-rw-r--r--libanalysis/Cargo.toml1
-rw-r--r--libanalysis/src/lib.rs14
-rw-r--r--libeditor/Cargo.toml3
-rw-r--r--libeditor/src/lib.rs2
-rw-r--r--libeditor/src/line_index.rs56
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 @@
1use languageserver_types::ServerCapabilities; 1use languageserver_types::{
2 ServerCapabilities,
3 TextDocumentSyncCapability,
4 TextDocumentSyncOptions,
5 TextDocumentSyncKind,
6};
2 7
3pub const SERVER_CAPABILITIES: ServerCapabilities = ServerCapabilities { 8pub 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
10use ::{ 10use ::{
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
16pub struct Responder<R: Request> { 16pub struct Responder<R: Request> {
@@ -52,7 +52,7 @@ impl<R: Request> Responder<R>
52} 52}
53 53
54 54
55pub fn parse_as<R>(raw: RawRequest) -> Result<::std::result::Result<(R::Params, Responder<R>), RawRequest>> 55pub 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
74pub fn expect<R>(io: &mut Io, raw: RawRequest) -> Result<Option<(R::Params, Responder<R>)>> 74pub 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
90pub 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
102pub 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
90pub fn unknown_method(io: &mut Io, raw: RawRequest) -> Result<()> { 121pub 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 @@
1use libanalysis::World;
2use libeditor;
3use {req, Result};
4
5pub 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;
19mod caps; 19mod caps;
20mod req; 20mod req;
21mod dispatch; 21mod dispatch;
22mod handlers;
23
24use std::path::PathBuf;
22 25
23use threadpool::ThreadPool; 26use threadpool::ThreadPool;
24use crossbeam_channel::{bounded, Sender, Receiver}; 27use crossbeam_channel::{bounded, Sender, Receiver};
25use flexi_logger::Logger; 28use flexi_logger::Logger;
26use libanalysis::WorldState; 29use libanalysis::WorldState;
30use languageserver_types::{TextDocumentItem, VersionedTextDocumentIdentifier, TextDocumentIdentifier};
27 31
28use ::{ 32use ::{
29 io::{Io, RawMsg}, 33 io::{Io, RawMsg},
34 handlers::handle_syntax_tree,
30}; 35};
31 36
32pub type Result<T> = ::std::result::Result<T, ::failure::Error>; 37pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
33 38
34fn main() -> Result<()> { 39fn 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
184trait FnBox<A, R>: Send { 208trait 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
218trait FilePath {
219 fn file_path(&self) -> Result<PathBuf>;
220}
221
222impl 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
229impl 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
236impl 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 @@
1use languageserver_types::TextDocumentIdentifier; 1use languageserver_types::{TextDocumentIdentifier, Range};
2pub use languageserver_types::request::*; 2
3pub use languageserver_types::{InitializeResult}; 3pub use languageserver_types::{
4 request::*, notification::*,
5 InitializeResult,
6};
4 7
5pub enum SyntaxTree {} 8pub 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")]
15pub struct SyntaxTreeParams { 18pub struct SyntaxTreeParams {
16 pub text_document: TextDocumentIdentifier 19 pub text_document: TextDocumentIdentifier
17} 20}
21
22pub enum ExtendSelection {}
23
24#[derive(Deserialize, Debug)]
25#[serde(rename_all = "camelCase")]
26pub struct ExtendSelectionParams {
27 pub text_document: TextDocumentIdentifier,
28 pub selections: Vec<Range>,
29}
30
31
32pub 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
20export function activate(context: vscode.ExtensionContext) { 21export 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
36export function deactivate(): Thenable<void> { 66export 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
120interface SyntaxTreeParams {
121 textDocument: TextDocumentIdentifier;
122}
123
124type SyntaxTreeResult = string
125
126interface ExtendSelectionParams {
127 textDocument: TextDocumentIdentifier;
128 selections: Range[];
129}
130
131interface 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"
4authors = ["Aleksey Kladov <[email protected]>"] 4authors = ["Aleksey Kladov <[email protected]>"]
5 5
6[dependencies] 6[dependencies]
7log = "0.4.2"
7failure = "0.1.2" 8failure = "0.1.2"
8parking_lot = "0.6.3" 9parking_lot = "0.6.3"
9libsyntax2 = { path = "../" } 10libsyntax2 = { 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 @@
1extern crate failure; 1extern crate failure;
2extern crate libsyntax2;
3extern crate parking_lot; 2extern crate parking_lot;
3#[macro_use]
4extern crate log;
5extern crate libsyntax2;
6
7mod arena;
4 8
5use std::{ 9use 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]>"]
5publish = false 5publish = false
6 6
7[dependencies] 7[dependencies]
8libsyntax2 = { path = "../" }
9itertools = "0.7.8" 8itertools = "0.7.8"
9superslice = "0.1.0"
10libsyntax2 = { 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 @@
1extern crate libsyntax2; 1extern crate libsyntax2;
2extern crate superslice;
2 3
3mod extend_selection; 4mod extend_selection;
5mod line_index;
4 6
5use libsyntax2::{ 7use 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 @@
1use superslice::Ext;
2use ::{TextUnit};
3
4pub struct LineIndex {
5 newlines: Vec<TextUnit>,
6}
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
9pub struct LineCol {
10 pub line: u32,
11 pub col: TextUnit,
12}
13
14impl 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]
36fn 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}