Skip to content

feat(completions): add textDocument/completion request to LSP + workspace handlers #164

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions crates/pg_completions/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ async-std = "1.12.0"

text-size.workspace = true

serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
pg_schema_cache.workspace = true
tree-sitter.workspace = true
tree_sitter_sql.workspace = true
Expand Down
5 changes: 3 additions & 2 deletions crates/pg_completions/src/complete.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use serde::{Deserialize, Serialize};
use text_size::TextSize;

use crate::{
Expand All @@ -17,9 +18,9 @@ pub struct CompletionParams<'a> {
pub tree: Option<&'a tree_sitter::Tree>,
}

#[derive(Debug, Default)]
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct CompletionResult {
pub items: Vec<CompletionItem>,
pub(crate) items: Vec<CompletionItem>,
}

impl IntoIterator for CompletionResult {
Expand Down
6 changes: 4 additions & 2 deletions crates/pg_completions/src/item.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#[derive(Debug, PartialEq, Eq)]
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum CompletionItemKind {
Table,
Function,
}

#[derive(Debug)]
#[derive(Debug, Serialize, Deserialize)]
pub struct CompletionItem {
pub label: String,
pub(crate) score: i32,
Expand Down
1 change: 1 addition & 0 deletions crates/pg_lsp_new/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pg_analyse = { workspace = true }
pg_configuration = { workspace = true }
pg_console = { workspace = true }
pg_diagnostics = { workspace = true }
pg_completions = { workspace = true }
pg_fs = { workspace = true }
pg_lsp_converters = { workspace = true }
pg_text_edit = { workspace = true }
Expand Down
22 changes: 20 additions & 2 deletions crates/pg_lsp_new/src/capabilities.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use pg_lsp_converters::{negotiated_encoding, PositionEncoding, WideEncoding};
use tower_lsp::lsp_types::{
ClientCapabilities, PositionEncodingKind, SaveOptions, ServerCapabilities,
ClientCapabilities, CompletionOptions, PositionEncodingKind, SaveOptions, ServerCapabilities,
TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
TextDocumentSyncSaveOptions,
TextDocumentSyncSaveOptions, WorkDoneProgressOptions,
};

/// The capabilities to send from server as part of [`InitializeResult`]
Expand All @@ -28,6 +28,24 @@ pub(crate) fn server_capabilities(capabilities: &ClientCapabilities) -> ServerCa
})),
},
)),
completion_provider: Some(CompletionOptions {
// currently not supporting the completionItem/resolve request.
// The request is used to get more information about a simple CompletionItem.
resolve_provider: None,

trigger_characters: Some(vec![".".to_owned(), ",".to_owned(), " ".to_owned()]),

// No character will lead to automatically inserting the selected completion-item
all_commit_characters: None,

// No special support for completionItem/resolve requests
completion_item: None,

// We do not report the progress of the completion process
work_done_progress_options: WorkDoneProgressOptions {
work_done_progress: None,
},
}),
document_formatting_provider: None,
document_range_formatting_provider: None,
document_on_type_formatting_provider: None,
Expand Down
1 change: 1 addition & 0 deletions crates/pg_lsp_new/src/handlers.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub(crate) mod completions;
pub(crate) mod text_document;
60 changes: 60 additions & 0 deletions crates/pg_lsp_new/src/handlers/completions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use crate::session::Session;
use anyhow::Result;
use pg_workspace_new::workspace;
use tower_lsp::lsp_types::{self, CompletionItem, CompletionItemLabelDetails};

#[tracing::instrument(level = "trace", skip_all)]
pub fn get_completions(
session: &Session,
params: lsp_types::CompletionParams,
) -> Result<lsp_types::CompletionResponse> {
let url = params.text_document_position.text_document.uri;
let path = session.file_path(&url)?;

let client_capabilities = session
.client_capabilities()
.expect("Client capabilities not established for current session.");

let line_index = session
.document(&url)
.map(|doc| doc.line_index)
.map_err(|_| anyhow::anyhow!("Document not found."))?;

let offset = pg_lsp_converters::from_proto::offset(
&line_index,
params.text_document_position.position,
pg_lsp_converters::negotiated_encoding(client_capabilities),
)?;

let completion_result = session
.workspace
.get_completions(workspace::CompletionParams {
path,
position: offset,
})?;

let items: Vec<CompletionItem> = completion_result
.into_iter()
.map(|i| CompletionItem {
label: i.label,
label_details: Some(CompletionItemLabelDetails {
description: Some(i.description),
detail: None,
}),
preselect: Some(i.preselected),
kind: Some(to_lsp_types_completion_item_kind(i.kind)),
..CompletionItem::default()
})
.collect();

Ok(lsp_types::CompletionResponse::Array(items))
}

fn to_lsp_types_completion_item_kind(
pg_comp_kind: pg_completions::CompletionItemKind,
) -> lsp_types::CompletionItemKind {
match pg_comp_kind {
pg_completions::CompletionItemKind::Function
| pg_completions::CompletionItemKind::Table => lsp_types::CompletionItemKind::CLASS,
}
}
6 changes: 3 additions & 3 deletions crates/pg_lsp_new/src/handlers/text_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use pg_workspace_new::workspace::{
ChangeFileParams, ChangeParams, CloseFileParams, GetFileContentParams, OpenFileParams,
};
use tower_lsp::lsp_types;
use tracing::{error, field};
use tracing::{error, field, info};

/// Handler for `textDocument/didOpen` LSP notification
#[tracing::instrument(
Expand All @@ -24,11 +24,11 @@ pub(crate) async fn did_open(
let version = params.text_document.version;
let content = params.text_document.text;

let biome_path = session.file_path(&url)?;
let path = session.file_path(&url)?;
let doc = Document::new(version, &content);

session.workspace.open_file(OpenFileParams {
path: biome_path,
path,
version,
content,
})?;
Expand Down
26 changes: 20 additions & 6 deletions crates/pg_lsp_new/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ impl LSPServer {
impl LanguageServer for LSPServer {
#[allow(deprecated)]
#[tracing::instrument(
level = "trace",
level = "info",
skip_all,
fields(
root_uri = params.root_uri.as_ref().map(display),
Expand Down Expand Up @@ -143,7 +143,7 @@ impl LanguageServer for LSPServer {
Ok(init)
}

#[tracing::instrument(level = "trace", skip(self))]
#[tracing::instrument(level = "info", skip_all)]
async fn initialized(&self, params: InitializedParams) {
let _ = params;

Expand All @@ -163,11 +163,12 @@ impl LanguageServer for LSPServer {
self.session.update_all_diagnostics().await;
}

#[tracing::instrument(level = "info", skip_all)]
async fn shutdown(&self) -> LspResult<()> {
Ok(())
}

#[tracing::instrument(level = "trace", skip(self))]
#[tracing::instrument(level = "info", skip_all)]
async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
let _ = params;
self.session.load_workspace_settings().await;
Expand Down Expand Up @@ -209,29 +210,41 @@ impl LanguageServer for LSPServer {
}
}

#[tracing::instrument(level = "trace", skip(self))]
async fn did_open(&self, params: DidOpenTextDocumentParams) {
handlers::text_document::did_open(&self.session, params)
.await
.ok();
}

#[tracing::instrument(level = "trace", skip(self, params))]
async fn did_change(&self, params: DidChangeTextDocumentParams) {
handlers::text_document::did_change(&self.session, params)
.await
.ok();
if let Err(e) = handlers::text_document::did_change(&self.session, params).await {
error!("{}", e);
};
}

#[tracing::instrument(level = "trace", skip(self))]
async fn did_save(&self, params: DidSaveTextDocumentParams) {
// handlers::text_document::did_save(&self.session, params)
// .await
// .ok();
}

#[tracing::instrument(level = "trace", skip(self))]
async fn did_close(&self, params: DidCloseTextDocumentParams) {
handlers::text_document::did_close(&self.session, params)
.await
.ok();
}

#[tracing::instrument(level = "trace", skip(self))]
async fn completion(&self, params: CompletionParams) -> LspResult<Option<CompletionResponse>> {
match handlers::completions::get_completions(&self.session, params) {
Ok(result) => LspResult::Ok(Some(result)),
Err(e) => LspResult::Err(into_lsp_error(e)),
}
}
}

impl Drop for LSPServer {
Expand Down Expand Up @@ -379,6 +392,7 @@ impl ServerFactory {
workspace_method!(builder, change_file);
workspace_method!(builder, close_file);
workspace_method!(builder, pull_diagnostics);
workspace_method!(builder, get_completions);

let (service, socket) = builder.finish();
ServerConnection { socket, service }
Expand Down
9 changes: 8 additions & 1 deletion crates/pg_lsp_new/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ use std::sync::Arc;
use std::sync::RwLock;
use tokio::sync::Notify;
use tokio::sync::OnceCell;
use tower_lsp::lsp_types;
use tower_lsp::lsp_types::Url;
use tower_lsp::lsp_types::{self, ClientCapabilities};
use tower_lsp::lsp_types::{MessageType, Registration};
use tower_lsp::lsp_types::{Unregistration, WorkspaceFolder};
use tracing::{error, info};
Expand Down Expand Up @@ -391,6 +391,13 @@ impl Session {
self.initialize_params.get()?.client_information.as_ref()
}

/// Returns a reference to the client capabilities for this session
pub(crate) fn client_capabilities(&self) -> Option<&ClientCapabilities> {
self.initialize_params
.get()
.map(|params| &params.client_capabilities)
}

/// This function attempts to read the `pglsp.toml` configuration file from
/// the root URI and update the workspace settings accordingly
#[tracing::instrument(level = "trace", skip(self))]
Expand Down
1 change: 1 addition & 0 deletions crates/pg_workspace_new/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pg_analyser = { workspace = true }
pg_configuration = { workspace = true }
pg_console = { workspace = true }
pg_diagnostics = { workspace = true }
pg_completions = { workspace = true }
pg_fs = { workspace = true, features = ["serde"] }
pg_query_ext = { workspace = true }
pg_schema_cache = { workspace = true }
Expand Down
6 changes: 6 additions & 0 deletions crates/pg_workspace_new/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ impl WorkspaceError {
pub fn vcs_disabled() -> Self {
Self::Vcs(VcsDiagnostic::DisabledVcs(DisabledVcs {}))
}

pub fn runtime(msg: &str) -> Self {
Self::RuntimeError(RuntimeError {
message: msg.into(),
})
}
}

impl Error for WorkspaceError {}
Expand Down
15 changes: 14 additions & 1 deletion crates/pg_workspace_new/src/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use pg_analyse::RuleCategories;
use pg_configuration::{PartialConfiguration, RuleSelector};
use pg_fs::PgLspPath;
use serde::{Deserialize, Serialize};
use text_size::TextRange;
use text_size::{TextRange, TextSize};

use crate::WorkspaceError;

Expand Down Expand Up @@ -40,6 +40,14 @@ pub struct PullDiagnosticsParams {
pub skip: Vec<RuleSelector>,
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct CompletionParams {
/// The File for which a completion is requested.
pub path: PgLspPath,
/// The Cursor position in the file for which a completion is requested.
pub position: TextSize,
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct PullDiagnosticsResult {
pub diagnostics: Vec<pg_diagnostics::serde::Diagnostic>,
Expand Down Expand Up @@ -106,6 +114,11 @@ pub trait Workspace: Send + Sync + RefUnwindSafe {
params: PullDiagnosticsParams,
) -> Result<PullDiagnosticsResult, WorkspaceError>;

fn get_completions(
&self,
params: CompletionParams,
) -> Result<pg_completions::CompletionResult, WorkspaceError>;

/// Refresh the schema cache for this workspace
fn refresh_schema_cache(&self) -> Result<(), WorkspaceError>;

Expand Down
7 changes: 7 additions & 0 deletions crates/pg_workspace_new/src/workspace/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,11 @@ where
) -> Result<super::PullDiagnosticsResult, WorkspaceError> {
self.request("pglsp/pull_diagnostics", params)
}

fn get_completions(
&self,
params: super::CompletionParams,
) -> Result<pg_completions::CompletionResult, WorkspaceError> {
self.request("pglsp/get_completions", params)
}
}
Loading
Loading