Add line index

This commit is contained in:
Aleksey Kladov 2018-08-10 21:13:39 +03:00
parent 1be7af26a8
commit 120789804d
11 changed files with 269 additions and 33 deletions

View File

@ -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,

View File

@ -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")
}

View 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))
}

View 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))
}
}

View File

@ -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>,
}

View File

@ -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[];
}

View File

@ -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 = "../" }

View File

@ -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();

View File

@ -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 = "../" }

View File

@ -1,6 +1,8 @@
extern crate libsyntax2;
extern crate superslice;
mod extend_selection;
mod line_index;
use libsyntax2::{
SyntaxNodeRef, AstNode,

View 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()});
}