aboutsummaryrefslogtreecommitdiff
path: root/crates/server
diff options
context:
space:
mode:
Diffstat (limited to 'crates/server')
-rw-r--r--crates/server/Cargo.toml18
-rw-r--r--crates/server/src/caps.rs36
-rw-r--r--crates/server/src/dispatch.rs174
-rw-r--r--crates/server/src/handlers.rs61
-rw-r--r--crates/server/src/io.rs202
-rw-r--r--crates/server/src/main.rs249
-rw-r--r--crates/server/src/req.rs41
7 files changed, 781 insertions, 0 deletions
diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml
new file mode 100644
index 000000000..e6d1b18c3
--- /dev/null
+++ b/crates/server/Cargo.toml
@@ -0,0 +1,18 @@
1[package]
2name = "m"
3version = "0.1.0"
4authors = ["Aleksey Kladov <[email protected]>"]
5
6[dependencies]
7failure = "0.1.2"
8languageserver-types = "0.48.0"
9serde_json = "1.0.24"
10serde = "1.0.71"
11serde_derive = "1.0.71"
12drop_bomb = "0.1.0"
13crossbeam-channel = "0.2.4"
14threadpool = "1.7.1"
15flexi_logger = "0.9.0"
16log = "0.4.3"
17libeditor = { path = "../libeditor" }
18libanalysis = { path = "../libanalysis" }
diff --git a/crates/server/src/caps.rs b/crates/server/src/caps.rs
new file mode 100644
index 000000000..3d89c64a9
--- /dev/null
+++ b/crates/server/src/caps.rs
@@ -0,0 +1,36 @@
1use languageserver_types::{
2 ServerCapabilities,
3 TextDocumentSyncCapability,
4 TextDocumentSyncOptions,
5 TextDocumentSyncKind,
6};
7
8pub const SERVER_CAPABILITIES: ServerCapabilities = ServerCapabilities {
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 )),
18 hover_provider: None,
19 completion_provider: None,
20 signature_help_provider: None,
21 definition_provider: None,
22 type_definition_provider: None,
23 implementation_provider: None,
24 references_provider: None,
25 document_highlight_provider: None,
26 document_symbol_provider: None,
27 workspace_symbol_provider: None,
28 code_action_provider: None,
29 code_lens_provider: None,
30 document_formatting_provider: None,
31 document_range_formatting_provider: None,
32 document_on_type_formatting_provider: None,
33 rename_provider: None,
34 color_provider: None,
35 execute_command_provider: None,
36};
diff --git a/crates/server/src/dispatch.rs b/crates/server/src/dispatch.rs
new file mode 100644
index 000000000..2da0996e3
--- /dev/null
+++ b/crates/server/src/dispatch.rs
@@ -0,0 +1,174 @@
1use std::marker::PhantomData;
2
3use serde::{
4 ser::Serialize,
5 de::DeserializeOwned,
6};
7use serde_json;
8use drop_bomb::DropBomb;
9
10use ::{
11 Result,
12 req::{Request, Notification},
13 io::{Io, RawMsg, RawResponse, RawRequest, RawNotification},
14};
15
16pub struct Responder<R: Request> {
17 id: u64,
18 bomb: DropBomb,
19 ph: PhantomData<R>,
20}
21
22impl<R: Request> Responder<R>
23 where
24 R::Params: DeserializeOwned,
25 R::Result: Serialize,
26{
27 pub fn response(self, io: &mut Io, resp: Result<R::Result>) -> Result<()> {
28 match resp {
29 Ok(res) => self.result(io, res)?,
30 Err(e) => {
31 self.error(io)?;
32 return Err(e);
33 }
34 }
35 Ok(())
36 }
37
38 pub fn result(mut self, io: &mut Io, result: R::Result) -> Result<()> {
39 self.bomb.defuse();
40 io.send(RawMsg::Response(RawResponse {
41 id: Some(self.id),
42 result: serde_json::to_value(result)?,
43 error: serde_json::Value::Null,
44 }));
45 Ok(())
46 }
47
48 pub fn error(mut self, io: &mut Io) -> Result<()> {
49 self.bomb.defuse();
50 error(io, self.id, ErrorCode::InternalError, "internal error")
51 }
52}
53
54
55fn parse_request_as<R>(raw: RawRequest) -> Result<::std::result::Result<(R::Params, Responder<R>), RawRequest>>
56 where
57 R: Request,
58 R::Params: DeserializeOwned,
59 R::Result: Serialize,
60{
61 if raw.method != R::METHOD {
62 return Ok(Err(raw));
63 }
64
65 let params: R::Params = serde_json::from_value(raw.params)?;
66 let responder = Responder {
67 id: raw.id,
68 bomb: DropBomb::new("dropped request"),
69 ph: PhantomData,
70 };
71 Ok(Ok((params, responder)))
72}
73
74pub 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
93pub fn expect_request<R>(io: &mut Io, raw: RawRequest) -> Result<Option<(R::Params, Responder<R>)>>
94 where
95 R: Request,
96 R::Params: DeserializeOwned,
97 R::Result: Serialize,
98{
99 let ret = match parse_request_as::<R>(raw)? {
100 Ok(x) => Some(x),
101 Err(raw) => {
102 unknown_method(io, raw)?;
103 None
104 }
105 };
106 Ok(ret)
107}
108
109fn parse_notification_as<N>(raw: RawNotification) -> Result<::std::result::Result<N::Params, RawNotification>>
110 where
111 N: Notification,
112 N::Params: DeserializeOwned,
113{
114 if raw.method != N::METHOD {
115 return Ok(Err(raw));
116 }
117 let params: N::Params = serde_json::from_value(raw.params)?;
118 Ok(Ok(params))
119}
120
121pub fn handle_notification<N, F>(not: &mut Option<RawNotification>, f: F) -> Result<()>
122 where
123 N: Notification,
124 N::Params: DeserializeOwned,
125 F: FnOnce(N::Params) -> Result<()>
126{
127 match not.take() {
128 None => Ok(()),
129 Some(n) => match parse_notification_as::<N>(n)? {
130 Ok(params) => f(params),
131 Err(n) => {
132 *not = Some(n);
133 Ok(())
134 },
135 }
136 }
137}
138
139
140pub fn unknown_method(io: &mut Io, raw: RawRequest) -> Result<()> {
141 error(io, raw.id, ErrorCode::MethodNotFound, "unknown method")
142}
143
144fn error(io: &mut Io, id: u64, code: ErrorCode, message: &'static str) -> Result<()> {
145 #[derive(Serialize)]
146 struct Error {
147 code: i32,
148 message: &'static str,
149 }
150 io.send(RawMsg::Response(RawResponse {
151 id: Some(id),
152 result: serde_json::Value::Null,
153 error: serde_json::to_value(Error {
154 code: code as i32,
155 message,
156 })?,
157 }));
158 Ok(())
159}
160
161
162#[allow(unused)]
163enum ErrorCode {
164 ParseError = -32700,
165 InvalidRequest = -32600,
166 MethodNotFound = -32601,
167 InvalidParams = -32602,
168 InternalError = -32603,
169 ServerErrorStart = -32099,
170 ServerErrorEnd = -32000,
171 ServerNotInitialized = -32002,
172 UnknownErrorCode = -32001,
173 RequestCancelled = -32800,
174}
diff --git a/crates/server/src/handlers.rs b/crates/server/src/handlers.rs
new file mode 100644
index 000000000..5ee87a4dd
--- /dev/null
+++ b/crates/server/src/handlers.rs
@@ -0,0 +1,61 @@
1use languageserver_types::{Range, Position};
2use libanalysis::World;
3use libeditor::{self, LineIndex, LineCol, TextRange, TextUnit};
4use {req, Result, FilePath};
5
6pub fn handle_syntax_tree(
7 world: World,
8 params: req::SyntaxTreeParams,
9) -> Result<String> {
10 let path = params.text_document.file_path()?;
11 let file = world.file_syntax(&path)?;
12 Ok(libeditor::syntax_tree(&file))
13}
14
15pub 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
33fn 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
40fn 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
50fn 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
57fn 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/crates/server/src/io.rs b/crates/server/src/io.rs
new file mode 100644
index 000000000..5eafc6942
--- /dev/null
+++ b/crates/server/src/io.rs
@@ -0,0 +1,202 @@
1use std::{
2 thread,
3 io::{
4 stdout, stdin,
5 BufRead, Write,
6 },
7};
8use serde_json::{Value, from_str, to_string};
9use crossbeam_channel::{Receiver, Sender, bounded};
10
11use Result;
12
13
14#[derive(Debug, Serialize, Deserialize)]
15#[serde(untagged)]
16pub enum RawMsg {
17 Request(RawRequest),
18 Notification(RawNotification),
19 Response(RawResponse),
20}
21
22#[derive(Debug, Serialize, Deserialize)]
23pub struct RawRequest {
24 pub id: u64,
25 pub method: String,
26 pub params: Value,
27}
28
29#[derive(Debug, Serialize, Deserialize)]
30pub struct RawNotification {
31 pub method: String,
32 pub params: Value,
33}
34
35#[derive(Debug, Serialize, Deserialize)]
36pub struct RawResponse {
37 pub id: Option<u64>,
38 pub result: Value,
39 pub error: Value,
40}
41
42struct MsgReceiver {
43 chan: Receiver<RawMsg>,
44 thread: Option<thread::JoinHandle<Result<()>>>,
45}
46
47impl MsgReceiver {
48 fn recv(&mut self) -> Result<RawMsg> {
49 match self.chan.recv() {
50 Some(msg) => Ok(msg),
51 None => {
52 self.cleanup()?;
53 unreachable!()
54 }
55 }
56 }
57
58 fn cleanup(&mut self) -> Result<()> {
59 self.thread
60 .take()
61 .ok_or_else(|| format_err!("MsgReceiver thread panicked"))?
62 .join()
63 .map_err(|_| format_err!("MsgReceiver thread panicked"))??;
64 bail!("client disconnected")
65 }
66
67 fn stop(self) -> Result<()> {
68 // Can't really self.thread.join() here, b/c it might be
69 // blocking on read
70 Ok(())
71 }
72}
73
74struct MsgSender {
75 chan: Sender<RawMsg>,
76 thread: thread::JoinHandle<Result<()>>,
77}
78
79impl MsgSender {
80 fn send(&mut self, msg: RawMsg) {
81 self.chan.send(msg)
82 }
83
84 fn stop(self) -> Result<()> {
85 drop(self.chan);
86 self.thread.join()
87 .map_err(|_| format_err!("MsgSender thread panicked"))??;
88 Ok(())
89 }
90}
91
92pub struct Io {
93 receiver: MsgReceiver,
94 sender: MsgSender,
95}
96
97impl Io {
98 pub fn from_stdio() -> Io {
99 let sender = {
100 let (tx, rx) = bounded(16);
101 MsgSender {
102 chan: tx,
103 thread: thread::spawn(move || {
104 let stdout = stdout();
105 let mut stdout = stdout.lock();
106 for msg in rx {
107 #[derive(Serialize)]
108 struct JsonRpc {
109 jsonrpc: &'static str,
110 #[serde(flatten)]
111 msg: RawMsg,
112 }
113 let text = to_string(&JsonRpc {
114 jsonrpc: "2.0",
115 msg,
116 })?;
117 write_msg_text(&mut stdout, &text)?;
118 }
119 Ok(())
120 }),
121 }
122 };
123 let receiver = {
124 let (tx, rx) = bounded(16);
125 MsgReceiver {
126 chan: rx,
127 thread: Some(thread::spawn(move || {
128 let stdin = stdin();
129 let mut stdin = stdin.lock();
130 while let Some(text) = read_msg_text(&mut stdin)? {
131 let msg: RawMsg = from_str(&text)?;
132 tx.send(msg);
133 }
134 Ok(())
135 })),
136 }
137 };
138 Io { receiver, sender }
139 }
140
141 pub fn send(&mut self, msg: RawMsg) {
142 self.sender.send(msg)
143 }
144
145 pub fn recv(&mut self) -> Result<RawMsg> {
146 self.receiver.recv()
147 }
148
149 pub fn receiver(&mut self) -> &mut Receiver<RawMsg> {
150 &mut self.receiver.chan
151 }
152
153 pub fn cleanup_receiver(&mut self) -> Result<()> {
154 self.receiver.cleanup()
155 }
156
157 pub fn stop(self) -> Result<()> {
158 self.receiver.stop()?;
159 self.sender.stop()?;
160 Ok(())
161 }
162}
163
164
165fn read_msg_text(inp: &mut impl BufRead) -> Result<Option<String>> {
166 let mut size = None;
167 let mut buf = String::new();
168 loop {
169 buf.clear();
170 if inp.read_line(&mut buf)? == 0 {
171 return Ok(None);
172 }
173 if !buf.ends_with("\r\n") {
174 bail!("malformed header: {:?}", buf);
175 }
176 let buf = &buf[..buf.len() - 2];
177 if buf.is_empty() {
178 break;
179 }
180 let mut parts = buf.splitn(2, ": ");
181 let header_name = parts.next().unwrap();
182 let header_value = parts.next().ok_or_else(|| format_err!("malformed header: {:?}", buf))?;
183 if header_name == "Content-Length" {
184 size = Some(header_value.parse::<usize>()?);
185 }
186 }
187 let size = size.ok_or_else(|| format_err!("no Content-Length"))?;
188 let mut buf = buf.into_bytes();
189 buf.resize(size, 0);
190 inp.read_exact(&mut buf)?;
191 let buf = String::from_utf8(buf)?;
192 debug!("< {}", buf);
193 Ok(Some(buf))
194}
195
196fn write_msg_text(out: &mut impl Write, msg: &str) -> Result<()> {
197 debug!("> {}", msg);
198 write!(out, "Content-Length: {}\r\n\r\n", msg.len())?;
199 out.write_all(msg.as_bytes())?;
200 out.flush()?;
201 Ok(())
202}
diff --git a/crates/server/src/main.rs b/crates/server/src/main.rs
new file mode 100644
index 000000000..116abce1c
--- /dev/null
+++ b/crates/server/src/main.rs
@@ -0,0 +1,249 @@
1#[macro_use]
2extern crate failure;
3#[macro_use]
4extern crate serde_derive;
5extern crate serde;
6extern crate serde_json;
7extern crate languageserver_types;
8extern crate drop_bomb;
9#[macro_use]
10extern crate crossbeam_channel;
11extern crate threadpool;
12#[macro_use]
13extern crate log;
14extern crate flexi_logger;
15extern crate libeditor;
16extern crate libanalysis;
17
18mod io;
19mod caps;
20mod req;
21mod dispatch;
22mod handlers;
23
24use std::path::PathBuf;
25
26use threadpool::ThreadPool;
27use crossbeam_channel::{bounded, Sender, Receiver};
28use flexi_logger::Logger;
29use libanalysis::WorldState;
30use languageserver_types::{TextDocumentItem, VersionedTextDocumentIdentifier, TextDocumentIdentifier};
31
32use ::{
33 io::{Io, RawMsg},
34 handlers::{handle_syntax_tree, handle_extend_selection},
35};
36
37pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
38
39fn main() -> Result<()> {
40 Logger::with_env_or_str("m=trace, libanalysis=trace")
41 .log_to_file()
42 .directory("log")
43 .start()?;
44 info!("starting server");
45 match ::std::panic::catch_unwind(|| main_inner()) {
46 Ok(res) => {
47 info!("shutting down: {:?}", res);
48 res
49 }
50 Err(_) => {
51 error!("server panicked");
52 bail!("server panicked")
53 }
54 }
55}
56
57fn main_inner() -> Result<()> {
58 let mut io = Io::from_stdio();
59 let res = initialize(&mut io);
60 info!("shutting down IO...");
61 let io_res = io.stop();
62 info!("... IO is down");
63 match (res, io_res) {
64 (Ok(()), Ok(())) => Ok(()),
65 (res, Ok(())) => res,
66 (Ok(()), io_res) => io_res,
67 (res, Err(io_err)) => {
68 error!("shutdown error: {:?}", io_err);
69 res
70 }
71 }
72}
73
74fn initialize(io: &mut Io) -> Result<()> {
75 loop {
76 match io.recv()? {
77 RawMsg::Request(req) => {
78 if let Some((_params, resp)) = dispatch::expect_request::<req::Initialize>(io, req)? {
79 resp.result(io, req::InitializeResult {
80 capabilities: caps::SERVER_CAPABILITIES
81 })?;
82 match io.recv()? {
83 RawMsg::Notification(n) => {
84 if n.method != "initialized" {
85 bail!("expected initialized notification");
86 }
87 }
88 _ => {
89 bail!("expected initialized notification");
90 }
91 }
92 return initialized(io);
93 }
94 }
95 RawMsg::Notification(n) => {
96 bail!("expected initialize request, got {:?}", n)
97 }
98 RawMsg::Response(res) => {
99 bail!("expected initialize request, got {:?}", res)
100 }
101 }
102 }
103}
104
105type Thunk = Box<for<'a> FnBox<&'a mut Io, Result<()>>>;
106
107fn initialized(io: &mut Io) -> Result<()> {
108 let mut world = WorldState::new();
109 let mut pool = ThreadPool::new(4);
110 let (sender, receiver) = bounded::<Thunk>(16);
111 let res = main_loop(io, &mut world, &mut pool, sender, receiver.clone());
112 info!("waiting for background jobs to finish...");
113 receiver.for_each(drop);
114 pool.join();
115 info!("...background jobs have finished");
116 res
117}
118
119fn main_loop(
120 io: &mut Io,
121 world: &mut WorldState,
122 pool: &mut ThreadPool,
123 sender: Sender<Thunk>,
124 receiver: Receiver<Thunk>,
125) -> Result<()> {
126 info!("server initialized, serving requests");
127 loop {
128 enum Event {
129 Msg(RawMsg),
130 Thunk(Thunk),
131 ReceiverDead,
132 }
133
134 let event = select! {
135 recv(io.receiver(), msg) => match msg {
136 Some(msg) => Event::Msg(msg),
137 None => Event::ReceiverDead,
138 },
139 recv(receiver, thunk) => Event::Thunk(thunk.unwrap()),
140 };
141
142 let msg = match event {
143 Event::ReceiverDead => {
144 io.cleanup_receiver()?;
145 unreachable!();
146 }
147 Event::Thunk(thunk) => {
148 thunk.call_box(io)?;
149 continue;
150 }
151 Event::Msg(msg) => msg,
152 };
153
154 match msg {
155 RawMsg::Request(req) => {
156 let mut req = Some(req);
157 dispatch::handle_request::<req::SyntaxTree, _>(&mut req, |params, resp| {
158 let world = world.snapshot();
159 let sender = sender.clone();
160 pool.execute(move || {
161 let res = handle_syntax_tree(world, params);
162 sender.send(Box::new(|io: &mut Io| resp.response(io, res)))
163 });
164 Ok(())
165 })?;
166 dispatch::handle_request::<req::ExtendSelection, _>(&mut req, |params, resp| {
167 let world = world.snapshot();
168 let sender = sender.clone();
169 pool.execute(move || {
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| {
176 resp.result(io, ())?;
177 Ok(())
178 })?;
179 if let Some(req) = req {
180 error!("unknown method: {:?}", req);
181 dispatch::unknown_method(io, req)?;
182 }
183 }
184 RawMsg::Notification(not) => {
185 let mut not = Some(not);
186 dispatch::handle_notification::<req::DidOpenTextDocument, _>(&mut not, |params| {
187 let path = params.text_document.file_path()?;
188 world.change_overlay(path, Some(params.text_document.text));
189 Ok(())
190 })?;
191 dispatch::handle_notification::<req::DidChangeTextDocument, _>(&mut not, |mut params| {
192 let path = params.text_document.file_path()?;
193 let text = params.content_changes.pop()
194 .ok_or_else(|| format_err!("empty changes"))?
195 .text;
196 world.change_overlay(path, Some(text));
197 Ok(())
198 })?;
199 dispatch::handle_notification::<req::DidCloseTextDocument, _>(&mut not, |params| {
200 let path = params.text_document.file_path()?;
201 world.change_overlay(path, None);
202 Ok(())
203 })?;
204
205 if let Some(not) = not {
206 error!("unhandled notification: {:?}", not)
207 }
208 }
209 msg => {
210 eprintln!("msg = {:?}", msg);
211 }
212 }
213 }
214}
215
216trait FnBox<A, R>: Send {
217 fn call_box(self: Box<Self>, a: A) -> R;
218}
219
220impl<A, R, F: FnOnce(A) -> R + Send> FnBox<A, R> for F {
221 fn call_box(self: Box<F>, a: A) -> R {
222 (*self)(a)
223 }
224}
225
226trait FilePath {
227 fn file_path(&self) -> Result<PathBuf>;
228}
229
230impl FilePath for TextDocumentItem {
231 fn file_path(&self) -> Result<PathBuf> {
232 self.uri.to_file_path()
233 .map_err(|()| format_err!("invalid uri: {}", self.uri))
234 }
235}
236
237impl FilePath for VersionedTextDocumentIdentifier {
238 fn file_path(&self) -> Result<PathBuf> {
239 self.uri.to_file_path()
240 .map_err(|()| format_err!("invalid uri: {}", self.uri))
241 }
242}
243
244impl FilePath for TextDocumentIdentifier {
245 fn file_path(&self) -> Result<PathBuf> {
246 self.uri.to_file_path()
247 .map_err(|()| format_err!("invalid uri: {}", self.uri))
248 }
249}
diff --git a/crates/server/src/req.rs b/crates/server/src/req.rs
new file mode 100644
index 000000000..4e588159b
--- /dev/null
+++ b/crates/server/src/req.rs
@@ -0,0 +1,41 @@
1use languageserver_types::{TextDocumentIdentifier, Range};
2
3pub use languageserver_types::{
4 request::*, notification::*,
5 InitializeResult,
6};
7
8pub enum SyntaxTree {}
9
10impl Request for SyntaxTree {
11 type Params = SyntaxTreeParams;
12 type Result = String;
13 const METHOD: &'static str = "m/syntaxTree";
14}
15
16#[derive(Deserialize, Debug)]
17#[serde(rename_all = "camelCase")]
18pub struct SyntaxTreeParams {
19 pub text_document: TextDocumentIdentifier
20}
21
22pub enum ExtendSelection {}
23
24impl Request for ExtendSelection {
25 type Params = ExtendSelectionParams;
26 type Result = ExtendSelectionResult;
27 const METHOD: &'static str = "m/extendSelection";
28}
29
30#[derive(Deserialize, Debug)]
31#[serde(rename_all = "camelCase")]
32pub struct ExtendSelectionParams {
33 pub text_document: TextDocumentIdentifier,
34 pub selections: Vec<Range>,
35}
36
37#[derive(Serialize, Debug)]
38#[serde(rename_all = "camelCase")]
39pub struct ExtendSelectionResult {
40 pub selections: Vec<Range>,
41}