Skip to content

Commit

Permalink
feat: Implemented semantic code completion
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael-F-Bryan committed Apr 2, 2024
1 parent 5457244 commit 5926700
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 7 deletions.
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/wit-language-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ rust-version.workspace = true
[dependencies]
clap = { workspace = true }
color-eyre = { workspace = true }
either = "1.10.0"
im = { workspace = true }
salsa = { workspace = true }
serde = { workspace = true }
Expand Down
57 changes: 57 additions & 0 deletions crates/wit-language-server/src/ops/completion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use either::Either;
use tower_lsp::lsp_types::{
CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse,
};
use wit_compiler::{
ast::{self, AstNode},
queries::{SourceFile, Workspace},
};

pub fn complete(
db: &dyn wit_compiler::Db,
_ws: Workspace,
file: SourceFile,
params: CompletionParams,
) -> Option<CompletionResponse> {
let point = crate::utils::position_to_ts(params.text_document_position.position);

let mut completions = Vec::new();

// First, add all known keywords
let keywords = wit_compiler::ast::KEYWORDS.iter().map(|kw| CompletionItem {
label: kw.to_string(),
kind: Some(CompletionItemKind::KEYWORD),
..Default::default()
});
completions.extend(keywords);

// Now, we do the hard job of figuring out which identifiers are in scope.
// FIXME: This doesn't take imported items into account
let items = wit_compiler::queries::file_items(db, file);
if let Some(index) = items.enclosing_item(db, point) {
let types = match index {
Either::Left(index) => items.get_world(db, index).items(db),
Either::Right(index) => items.get_interface(db, index).items(db),
};

completions.extend(types.names().map(|label| CompletionItem {
label: label.to_string(),
..Default::default()
}));
}

let ast = wit_compiler::queries::parse(db, file);
if let Some(ident) = ast
.tree(db)
.ancestors(point)
.find_map(ast::Identifier::cast)
{
let src = file.contents(db);
let ident = ident.value(&src);
// The user has started writing an identifier, so limit the completions
// to whatever might match what they've written.
completions.retain(|c| c.label.starts_with(ident));
}

Some(CompletionResponse::Array(completions))
}
3 changes: 2 additions & 1 deletion crates/wit-language-server/src/ops/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod completion;
mod diagnostics;
mod fold;

pub use self::{diagnostics::file_diagnostics, fold::folding_range};
pub use self::{completion::complete, diagnostics::file_diagnostics, fold::folding_range};
27 changes: 21 additions & 6 deletions crates/wit-language-server/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@ use tokio::sync::{Mutex, MutexGuard};
use tower_lsp::{
jsonrpc::Error,
lsp_types::{
DiagnosticOptions, DiagnosticServerCapabilities, DidChangeTextDocumentParams,
DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams,
DocumentDiagnosticParams, DocumentDiagnosticReportResult, FoldingRange, FoldingRangeParams,
InitializeParams, InitializeResult, SelectionRange, SelectionRangeParams,
ServerCapabilities, ServerInfo, TextDocumentContentChangeEvent, TextDocumentItem,
TextDocumentSyncCapability, TextDocumentSyncKind, Url,
CompletionOptions, CompletionParams, CompletionResponse, DiagnosticOptions, DiagnosticServerCapabilities, DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentDiagnosticParams, DocumentDiagnosticReportResult, FoldingRange, FoldingRangeParams, InitializeParams, InitializeResult, SelectionRange, SelectionRangeParams, ServerCapabilities, ServerInfo, TextDocumentContentChangeEvent, TextDocumentItem, TextDocumentSyncCapability, TextDocumentSyncKind, Url
},
Client, ClientSocket, LspService,
};
Expand Down Expand Up @@ -108,6 +103,7 @@ impl tower_lsp::LanguageServer for LanguageServer {
..Default::default()
},
)),
completion_provider: Some(CompletionOptions::default()),
..Default::default()
},
server_info: Some(ServerInfo {
Expand Down Expand Up @@ -248,10 +244,13 @@ impl tower_lsp::LanguageServer for LanguageServer {
Ok(Some(ranges))
}

#[tracing::instrument(level = "debug", skip_all)]
async fn diagnostic(
&self,
params: DocumentDiagnosticParams,
) -> Result<DocumentDiagnosticReportResult, Error> {
tracing::debug!(document.uri=%params.text_document.uri);

let snap = self.snapshot().await;
let db = snap.wit_db();
let path = &params.text_document.uri;
Expand All @@ -263,6 +262,22 @@ impl tower_lsp::LanguageServer for LanguageServer {

Ok(crate::ops::file_diagnostics(db, snap.ws, file))
}

#[tracing::instrument(level = "debug", skip_all)]
async fn completion(
&self,
params: CompletionParams,
) -> Result<Option<CompletionResponse>, Error> {
let path = &params.text_document_position.text_document.uri;
tracing::debug!(document.uri=%path);
let snap = self.snapshot().await;

let Some(file) = snap.ws.lookup(snap.wit_db(), path.as_str()) else {
return Ok(None);
};

Ok(crate::ops::complete(snap.wit_db(), snap.ws, file, params))
}
}

fn selection_range(first: tree_sitter::Range, rest: Vector<tree_sitter::Range>) -> SelectionRange {
Expand Down
7 changes: 7 additions & 0 deletions crates/wit-language-server/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,10 @@ pub fn location_to_lsp(
range: ts_to_range(range),
}
}

pub fn position_to_ts(position: tower_lsp::lsp_types::Position) -> tree_sitter::Point {
tree_sitter::Point {
row: position.line as usize,
column: position.character as usize,
}
}

0 comments on commit 5926700

Please sign in to comment.