mirror of
https://github.com/rust-lang/rust.git
synced 2025-02-18 01:44:04 +00:00
Add line index
This commit is contained in:
parent
1be7af26a8
commit
120789804d
@ -1,7 +1,20 @@
|
||||
use languageserver_types::ServerCapabilities;
|
||||
use languageserver_types::{
|
||||
ServerCapabilities,
|
||||
TextDocumentSyncCapability,
|
||||
TextDocumentSyncOptions,
|
||||
TextDocumentSyncKind,
|
||||
};
|
||||
|
||||
pub const SERVER_CAPABILITIES: ServerCapabilities = ServerCapabilities {
|
||||
text_document_sync: None,
|
||||
text_document_sync: Some(TextDocumentSyncCapability::Options(
|
||||
TextDocumentSyncOptions {
|
||||
open_close: Some(true),
|
||||
change: Some(TextDocumentSyncKind::Full),
|
||||
will_save: None,
|
||||
will_save_wait_until: None,
|
||||
save: None,
|
||||
}
|
||||
)),
|
||||
hover_provider: None,
|
||||
completion_provider: None,
|
||||
signature_help_provider: None,
|
||||
|
@ -9,8 +9,8 @@ use drop_bomb::DropBomb;
|
||||
|
||||
use ::{
|
||||
Result,
|
||||
req::Request,
|
||||
io::{Io, RawMsg, RawResponse, RawRequest},
|
||||
req::{Request, Notification},
|
||||
io::{Io, RawMsg, RawResponse, RawRequest, RawNotification},
|
||||
};
|
||||
|
||||
pub struct Responder<R: Request> {
|
||||
@ -52,7 +52,7 @@ impl<R: Request> Responder<R>
|
||||
}
|
||||
|
||||
|
||||
pub fn parse_as<R>(raw: RawRequest) -> Result<::std::result::Result<(R::Params, Responder<R>), RawRequest>>
|
||||
pub fn parse_request_as<R>(raw: RawRequest) -> Result<::std::result::Result<(R::Params, Responder<R>), RawRequest>>
|
||||
where
|
||||
R: Request,
|
||||
R::Params: DeserializeOwned,
|
||||
@ -71,13 +71,13 @@ pub fn parse_as<R>(raw: RawRequest) -> Result<::std::result::Result<(R::Params,
|
||||
Ok(Ok((params, responder)))
|
||||
}
|
||||
|
||||
pub fn expect<R>(io: &mut Io, raw: RawRequest) -> Result<Option<(R::Params, Responder<R>)>>
|
||||
pub fn expect_request<R>(io: &mut Io, raw: RawRequest) -> Result<Option<(R::Params, Responder<R>)>>
|
||||
where
|
||||
R: Request,
|
||||
R::Params: DeserializeOwned,
|
||||
R::Result: Serialize,
|
||||
{
|
||||
let ret = match parse_as::<R>(raw)? {
|
||||
let ret = match parse_request_as::<R>(raw)? {
|
||||
Ok(x) => Some(x),
|
||||
Err(raw) => {
|
||||
unknown_method(io, raw)?;
|
||||
@ -87,6 +87,37 @@ pub fn expect<R>(io: &mut Io, raw: RawRequest) -> Result<Option<(R::Params, Resp
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
pub fn parse_notification_as<N>(raw: RawNotification) -> Result<::std::result::Result<N::Params, RawNotification>>
|
||||
where
|
||||
N: Notification,
|
||||
N::Params: DeserializeOwned,
|
||||
{
|
||||
if raw.method != N::METHOD {
|
||||
return Ok(Err(raw));
|
||||
}
|
||||
let params: N::Params = serde_json::from_value(raw.params)?;
|
||||
Ok(Ok(params))
|
||||
}
|
||||
|
||||
pub fn handle_notification<N, F>(not: &mut Option<RawNotification>, f: F) -> Result<()>
|
||||
where
|
||||
N: Notification,
|
||||
N::Params: DeserializeOwned,
|
||||
F: FnOnce(N::Params) -> Result<()>
|
||||
{
|
||||
match not.take() {
|
||||
None => Ok(()),
|
||||
Some(n) => match parse_notification_as::<N>(n)? {
|
||||
Ok(params) => f(params),
|
||||
Err(n) => {
|
||||
*not = Some(n);
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn unknown_method(io: &mut Io, raw: RawRequest) -> Result<()> {
|
||||
error(io, raw.id, ErrorCode::MethodNotFound, "unknown method")
|
||||
}
|
||||
|
13
codeless/server/src/handlers.rs
Normal file
13
codeless/server/src/handlers.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use libanalysis::World;
|
||||
use libeditor;
|
||||
use {req, Result};
|
||||
|
||||
pub fn handle_syntax_tree(
|
||||
world: World,
|
||||
params: req::SyntaxTreeParams
|
||||
) -> Result<String> {
|
||||
let path = params.text_document.uri.to_file_path()
|
||||
.map_err(|()| format_err!("invalid path"))?;
|
||||
let file = world.file_syntax(&path)?;
|
||||
Ok(libeditor::syntax_tree(&file))
|
||||
}
|
@ -19,20 +19,25 @@ mod io;
|
||||
mod caps;
|
||||
mod req;
|
||||
mod dispatch;
|
||||
mod handlers;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use threadpool::ThreadPool;
|
||||
use crossbeam_channel::{bounded, Sender, Receiver};
|
||||
use flexi_logger::Logger;
|
||||
use libanalysis::WorldState;
|
||||
use languageserver_types::{TextDocumentItem, VersionedTextDocumentIdentifier, TextDocumentIdentifier};
|
||||
|
||||
use ::{
|
||||
io::{Io, RawMsg},
|
||||
handlers::handle_syntax_tree,
|
||||
};
|
||||
|
||||
pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
Logger::with_env_or_str("m=trace")
|
||||
Logger::with_env_or_str("m=trace, libanalysis=trace")
|
||||
.log_to_file()
|
||||
.directory("log")
|
||||
.start()?;
|
||||
@ -70,7 +75,7 @@ fn initialize(io: &mut Io) -> Result<()> {
|
||||
loop {
|
||||
match io.recv()? {
|
||||
RawMsg::Request(req) => {
|
||||
if let Some((_params, resp)) = dispatch::expect::<req::Initialize>(io, req)? {
|
||||
if let Some((_params, resp)) = dispatch::expect_request::<req::Initialize>(io, req)? {
|
||||
resp.result(io, req::InitializeResult {
|
||||
capabilities: caps::SERVER_CAPABILITIES
|
||||
})?;
|
||||
@ -148,18 +153,12 @@ fn main_loop(
|
||||
|
||||
match msg {
|
||||
RawMsg::Request(req) => {
|
||||
let req = match dispatch::parse_as::<req::SyntaxTree>(req)? {
|
||||
let req = match dispatch::parse_request_as::<req::SyntaxTree>(req)? {
|
||||
Ok((params, resp)) => {
|
||||
let world = world.snapshot();
|
||||
let sender = sender.clone();
|
||||
pool.execute(move || {
|
||||
let res: Result<String> = (|| {
|
||||
let path = params.text_document.uri.to_file_path()
|
||||
.map_err(|()| format_err!("invalid path"))?;
|
||||
let file = world.file_syntax(&path)?;
|
||||
Ok(libeditor::syntax_tree(&file))
|
||||
})();
|
||||
|
||||
let res: Result<String> = handle_syntax_tree(world, params);
|
||||
sender.send(Box::new(|io: &mut Io| resp.response(io, res)))
|
||||
});
|
||||
continue;
|
||||
@ -167,12 +166,38 @@ fn main_loop(
|
||||
Err(req) => req,
|
||||
};
|
||||
|
||||
if let Some(((), resp)) = dispatch::expect::<req::Shutdown>(io, req)? {
|
||||
info!("shutdown request");
|
||||
if let Some(((), resp)) = dispatch::expect_request::<req::Shutdown>(io, req)? {
|
||||
info!("clean shutdown started");
|
||||
resp.result(io, ())?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
RawMsg::Notification(not) => {
|
||||
use dispatch::handle_notification as h;
|
||||
let mut not = Some(not);
|
||||
h::<req::DidOpenTextDocument, _>(&mut not, |params| {
|
||||
let path = params.text_document.file_path()?;
|
||||
world.change_overlay(path, Some(params.text_document.text));
|
||||
Ok(())
|
||||
})?;
|
||||
h::<req::DidChangeTextDocument, _>(&mut not, |mut params| {
|
||||
let path = params.text_document.file_path()?;
|
||||
let text = params.content_changes.pop()
|
||||
.ok_or_else(|| format_err!("empty changes"))?
|
||||
.text;
|
||||
world.change_overlay(path, Some(text));
|
||||
Ok(())
|
||||
})?;
|
||||
h::<req::DidCloseTextDocument, _>(&mut not, |params| {
|
||||
let path = params.text_document.file_path()?;
|
||||
world.change_overlay(path, None);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
if let Some(not) = not {
|
||||
error!("unhandled notification: {:?}", not)
|
||||
}
|
||||
}
|
||||
msg => {
|
||||
eprintln!("msg = {:?}", msg);
|
||||
}
|
||||
@ -180,7 +205,6 @@ fn main_loop(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
trait FnBox<A, R>: Send {
|
||||
fn call_box(self: Box<Self>, a: A) -> R;
|
||||
}
|
||||
@ -190,3 +214,28 @@ impl<A, R, F: FnOnce(A) -> R + Send> FnBox<A, R> for F {
|
||||
(*self)(a)
|
||||
}
|
||||
}
|
||||
|
||||
trait FilePath {
|
||||
fn file_path(&self) -> Result<PathBuf>;
|
||||
}
|
||||
|
||||
impl FilePath for TextDocumentItem {
|
||||
fn file_path(&self) -> Result<PathBuf> {
|
||||
self.uri.to_file_path()
|
||||
.map_err(|()| format_err!("invalid uri: {}", self.uri))
|
||||
}
|
||||
}
|
||||
|
||||
impl FilePath for VersionedTextDocumentIdentifier {
|
||||
fn file_path(&self) -> Result<PathBuf> {
|
||||
self.uri.to_file_path()
|
||||
.map_err(|()| format_err!("invalid uri: {}", self.uri))
|
||||
}
|
||||
}
|
||||
|
||||
impl FilePath for TextDocumentIdentifier {
|
||||
fn file_path(&self) -> Result<PathBuf> {
|
||||
self.uri.to_file_path()
|
||||
.map_err(|()| format_err!("invalid uri: {}", self.uri))
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
use languageserver_types::TextDocumentIdentifier;
|
||||
pub use languageserver_types::request::*;
|
||||
pub use languageserver_types::{InitializeResult};
|
||||
use languageserver_types::{TextDocumentIdentifier, Range};
|
||||
|
||||
pub use languageserver_types::{
|
||||
request::*, notification::*,
|
||||
InitializeResult,
|
||||
};
|
||||
|
||||
pub enum SyntaxTree {}
|
||||
|
||||
@ -11,7 +14,21 @@ impl Request for SyntaxTree {
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all="camelCase")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SyntaxTreeParams {
|
||||
pub text_document: TextDocumentIdentifier
|
||||
}
|
||||
|
||||
pub enum ExtendSelection {}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExtendSelectionParams {
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
pub selections: Vec<Range>,
|
||||
}
|
||||
|
||||
|
||||
pub struct ExtendSelectionResult {
|
||||
pub selections: Vec<Range>,
|
||||
}
|
||||
|
@ -6,7 +6,8 @@ import {
|
||||
ServerOptions,
|
||||
TransportKind,
|
||||
Executable,
|
||||
TextDocumentIdentifier
|
||||
TextDocumentIdentifier,
|
||||
Range
|
||||
} from 'vscode-languageclient';
|
||||
|
||||
|
||||
@ -18,6 +19,7 @@ let uris = {
|
||||
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
let textDocumentContentProvider = new TextDocumentContentProvider()
|
||||
let dispose = (disposable) => {
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
@ -26,11 +28,39 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
|
||||
registerCommand('libsyntax-rust.syntaxTree', () => openDoc(uris.syntaxTree))
|
||||
registerCommand('libsyntax-rust.extendSelection', async () => {
|
||||
let editor = vscode.window.activeTextEditor
|
||||
if (editor == null || editor.document.languageId != "rust") return
|
||||
let request: ExtendSelectionParams = {
|
||||
textDocument: { uri: editor.document.uri.toString() },
|
||||
selections: editor.selections.map((s) => {
|
||||
let r: Range = { start: s.start, end: s.end }
|
||||
return r;
|
||||
})
|
||||
}
|
||||
let response = await client.sendRequest<ExtendSelectionResult>("m/extendSelection", request)
|
||||
editor.selections = response.selections.map((range) => {
|
||||
return new vscode.Selection(
|
||||
new vscode.Position(range.start.line, range.start.character),
|
||||
new vscode.Position(range.end.line, range.end.character),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
dispose(vscode.workspace.registerTextDocumentContentProvider(
|
||||
'libsyntax-rust',
|
||||
new TextDocumentContentProvider()
|
||||
textDocumentContentProvider
|
||||
))
|
||||
startServer()
|
||||
vscode.workspace.onDidChangeTextDocument((event: vscode.TextDocumentChangeEvent) => {
|
||||
let doc = event.document
|
||||
if (doc.languageId != "rust") return
|
||||
// We need to order this after LS updates, but there's no API for that.
|
||||
// Hence, good old setTimeout.
|
||||
setTimeout(() => {
|
||||
textDocumentContentProvider.eventEmitter.fire(uris.syntaxTree)
|
||||
}, 10)
|
||||
}, null, context.subscriptions)
|
||||
}
|
||||
|
||||
export function deactivate(): Thenable<void> {
|
||||
@ -76,11 +106,28 @@ class TextDocumentContentProvider implements vscode.TextDocumentContentProvider
|
||||
public provideTextDocumentContent(uri: vscode.Uri): vscode.ProviderResult<string> {
|
||||
let editor = vscode.window.activeTextEditor;
|
||||
if (editor == null) return ""
|
||||
let textDocument: TextDocumentIdentifier = { uri: editor.document.uri.toString() };
|
||||
return client.sendRequest("m/syntaxTree", { textDocument })
|
||||
let request: SyntaxTreeParams = {
|
||||
textDocument: { uri: editor.document.uri.toString() }
|
||||
};
|
||||
return client.sendRequest<SyntaxTreeResult>("m/syntaxTree", request);
|
||||
}
|
||||
|
||||
get onDidChange(): vscode.Event<vscode.Uri> {
|
||||
return this.eventEmitter.event
|
||||
}
|
||||
}
|
||||
|
||||
interface SyntaxTreeParams {
|
||||
textDocument: TextDocumentIdentifier;
|
||||
}
|
||||
|
||||
type SyntaxTreeResult = string
|
||||
|
||||
interface ExtendSelectionParams {
|
||||
textDocument: TextDocumentIdentifier;
|
||||
selections: Range[];
|
||||
}
|
||||
|
||||
interface ExtendSelectionResult {
|
||||
selections: Range[];
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Aleksey Kladov <aleksey.kladov@gmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.2"
|
||||
failure = "0.1.2"
|
||||
parking_lot = "0.6.3"
|
||||
libsyntax2 = { path = "../" }
|
||||
|
@ -1,6 +1,10 @@
|
||||
extern crate failure;
|
||||
extern crate libsyntax2;
|
||||
extern crate parking_lot;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate libsyntax2;
|
||||
|
||||
mod arena;
|
||||
|
||||
use std::{
|
||||
fs,
|
||||
@ -66,8 +70,10 @@ impl World {
|
||||
return Ok(file.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let file = self.with_file_text(path, ast::File::parse)?;
|
||||
let file = self.with_file_text(path, |text| {
|
||||
trace!("parsing file: {}", path.display());
|
||||
ast::File::parse(text)
|
||||
})?;
|
||||
let mut guard = self.data.file_map.write();
|
||||
let file = guard.entry(path.to_owned())
|
||||
.or_insert(file)
|
||||
@ -86,7 +92,7 @@ impl World {
|
||||
return Ok(f(&*text));
|
||||
}
|
||||
}
|
||||
|
||||
trace!("loading file from disk: {}", path.display());
|
||||
let text = fs::read_to_string(path)?;
|
||||
{
|
||||
let mut guard = self.data.fs_map.write();
|
||||
|
@ -5,5 +5,6 @@ authors = ["Aleksey Kladov <aleksey.kladov@gmail.com>"]
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
libsyntax2 = { path = "../" }
|
||||
itertools = "0.7.8"
|
||||
superslice = "0.1.0"
|
||||
libsyntax2 = { path = "../" }
|
||||
|
@ -1,6 +1,8 @@
|
||||
extern crate libsyntax2;
|
||||
extern crate superslice;
|
||||
|
||||
mod extend_selection;
|
||||
mod line_index;
|
||||
|
||||
use libsyntax2::{
|
||||
SyntaxNodeRef, AstNode,
|
||||
|
56
libeditor/src/line_index.rs
Normal file
56
libeditor/src/line_index.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use superslice::Ext;
|
||||
use ::{TextUnit};
|
||||
|
||||
pub struct LineIndex {
|
||||
newlines: Vec<TextUnit>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct LineCol {
|
||||
pub line: u32,
|
||||
pub col: TextUnit,
|
||||
}
|
||||
|
||||
impl LineIndex {
|
||||
pub fn new(text: &str) -> LineIndex {
|
||||
let mut newlines = vec![0.into()];
|
||||
let mut curr = 0.into();
|
||||
for c in text.chars() {
|
||||
curr += TextUnit::of_char(c);
|
||||
if c == '\n' {
|
||||
newlines.push(curr);
|
||||
}
|
||||
}
|
||||
LineIndex { newlines }
|
||||
}
|
||||
|
||||
pub fn translate(&self, offset: TextUnit) -> LineCol {
|
||||
let line = self.newlines.upper_bound(&offset) - 1;
|
||||
let line_start_offset = self.newlines[line];
|
||||
let col = offset - line_start_offset;
|
||||
return LineCol { line: line as u32, col }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_line_index() {
|
||||
let text = "hello\nworld";
|
||||
let index = LineIndex::new(text);
|
||||
assert_eq!(index.translate(0.into()), LineCol { line: 0, col: 0.into()});
|
||||
assert_eq!(index.translate(1.into()), LineCol { line: 0, col: 1.into()});
|
||||
assert_eq!(index.translate(5.into()), LineCol { line: 0, col: 5.into()});
|
||||
assert_eq!(index.translate(6.into()), LineCol { line: 1, col: 0.into()});
|
||||
assert_eq!(index.translate(7.into()), LineCol { line: 1, col: 1.into()});
|
||||
assert_eq!(index.translate(8.into()), LineCol { line: 1, col: 2.into()});
|
||||
assert_eq!(index.translate(10.into()), LineCol { line: 1, col: 4.into()});
|
||||
assert_eq!(index.translate(11.into()), LineCol { line: 1, col: 5.into()});
|
||||
assert_eq!(index.translate(12.into()), LineCol { line: 1, col: 6.into()});
|
||||
|
||||
let text = "\nhello\nworld";
|
||||
let index = LineIndex::new(text);
|
||||
assert_eq!(index.translate(0.into()), LineCol { line: 0, col: 0.into()});
|
||||
assert_eq!(index.translate(1.into()), LineCol { line: 1, col: 0.into()});
|
||||
assert_eq!(index.translate(2.into()), LineCol { line: 1, col: 1.into()});
|
||||
assert_eq!(index.translate(6.into()), LineCol { line: 1, col: 5.into()});
|
||||
assert_eq!(index.translate(7.into()), LineCol { line: 2, col: 0.into()});
|
||||
}
|
Loading…
Reference in New Issue
Block a user