Skip to content

Commit 7ee86d2

Browse files
committed
feat: implement textDocument/formatting
This is done by spawning a `buildifier` process. Buildifier can be configured in the initialization options: ```json { "buildifier": { "path": "...", "args": ["...", "..."] } } ``` Closes #267
1 parent db21acd commit 7ee86d2

File tree

5 files changed

+90
-4
lines changed

5 files changed

+90
-4
lines changed

crates/starpls/src/commands/server.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ impl ServerCommand {
6262
}),
6363
declaration_provider: Some(DeclarationCapability::Simple(true)),
6464
definition_provider: Some(OneOf::Left(true)),
65+
document_formatting_provider: Some(OneOf::Left(true)),
6566
document_symbol_provider: Some(OneOf::Left(true)),
6667
hover_provider: Some(HoverProviderCapability::Simple(true)),
6768
references_provider: Some(OneOf::Left(true)),

crates/starpls/src/config.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,24 @@
11
use lsp_types::ClientCapabilities;
2+
use serde::Deserialize;
3+
use serde_json::Value;
24

35
use crate::commands::server::ServerCommand;
46

7+
#[derive(Deserialize, Default)]
8+
#[serde(default)]
9+
pub(crate) struct BuildifierConfig {
10+
pub(crate) path: Option<String>,
11+
pub(crate) args: Vec<String>,
12+
}
13+
14+
#[derive(Deserialize)]
15+
struct InitializationOptions {
16+
buildifier: Option<BuildifierConfig>,
17+
}
18+
519
#[derive(Default)]
620
pub(crate) struct ServerConfig {
21+
pub(crate) buildifier: Option<BuildifierConfig>,
722
pub(crate) args: ServerCommand,
823
pub(crate) caps: ClientCapabilities,
924
}
@@ -15,6 +30,13 @@ macro_rules! try_or_default {
1530
}
1631

1732
impl ServerConfig {
33+
pub(crate) fn from_json(value: Value) -> Self {
34+
let mut config = ServerConfig::default();
35+
if let Ok(opts) = serde_json::from_value::<InitializationOptions>(value) {
36+
config.buildifier = opts.buildifier;
37+
}
38+
config
39+
}
1840
pub(crate) fn has_text_document_definition_link_support(&self) -> bool {
1941
try_or_default!(self.caps.text_document.as_ref()?.definition?.link_support)
2042
}

crates/starpls/src/event_loop.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,10 @@ pub fn process_connection(
7777
initialize_params: InitializeParams,
7878
) -> anyhow::Result<()> {
7979
debug!("initializing state and starting event loop");
80-
let config = ServerConfig {
81-
args,
82-
caps: initialize_params.capabilities,
83-
};
80+
let mut config =
81+
ServerConfig::from_json(initialize_params.initialization_options.unwrap_or_default());
82+
config.args = args;
83+
config.caps = initialize_params.capabilities;
8484
let server = Server::new(connection, config)?;
8585
server.run()
8686
}
@@ -218,6 +218,7 @@ impl Server {
218218
.on::<lsp_types::request::HoverRequest>(requests::hover)
219219
.on::<lsp_types::request::References>(requests::find_references)
220220
.on::<lsp_types::request::SignatureHelpRequest>(requests::signature_help)
221+
.on::<lsp_types::request::Formatting>(requests::formatting)
221222
.finish();
222223
}
223224

crates/starpls/src/handlers/requests.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::io::Write;
2+
13
use anyhow::Ok;
24
use starpls_ide::CompletionItemKind;
35
use starpls_ide::CompletionMode::InsertText;
@@ -287,6 +289,62 @@ pub(crate) fn document_symbols(
287289
}))
288290
}
289291

292+
pub(crate) fn formatting(
293+
snapshot: &ServerSnapshot,
294+
params: lsp_types::DocumentFormattingParams,
295+
) -> anyhow::Result<Option<Vec<lsp_types::TextEdit>>> {
296+
let path = path_buf_from_url(&params.text_document.uri)?;
297+
let file_id = try_opt!(snapshot.document_manager.read().lookup_by_path_buf(&path));
298+
let line_index = try_opt!(snapshot.analysis_snapshot.line_index(file_id)?);
299+
let default_buildifier_config;
300+
let buildifier = match &snapshot.config.buildifier {
301+
Some(config) => config,
302+
None => {
303+
default_buildifier_config = Default::default();
304+
&default_buildifier_config
305+
}
306+
};
307+
308+
// Read the file's contents.
309+
let contents = try_opt!(snapshot.analysis_snapshot.file_contents(file_id)?);
310+
311+
// Spawn buildifier.
312+
let mut command =
313+
std::process::Command::new(buildifier.path.as_deref().unwrap_or("buildifier"));
314+
command.args(&buildifier.args);
315+
command.arg("-");
316+
let mut child = command
317+
.stdin(std::process::Stdio::piped())
318+
.stdout(std::process::Stdio::piped())
319+
.spawn()?;
320+
321+
// Write the file's contents to stdin.
322+
let mut stdin = child.stdin.take().unwrap();
323+
stdin.write_all(contents.as_bytes())?;
324+
drop(stdin);
325+
326+
// Read the formatted output from stdout.
327+
let output = child.wait_with_output()?;
328+
if !output.status.success() {
329+
return Ok(None);
330+
}
331+
let new_text = String::from_utf8(output.stdout)?;
332+
333+
// Replace the entire document with the formatted text.
334+
let range = lsp_types::Range {
335+
start: lsp_types::Position {
336+
line: 0,
337+
character: 0,
338+
},
339+
end: lsp_types::Position {
340+
line: line_index.len().into(),
341+
character: 0,
342+
},
343+
};
344+
345+
Ok(Some(vec![lsp_types::TextEdit { range, new_text }]))
346+
}
347+
290348
fn to_markup_doc(doc: String) -> lsp_types::Documentation {
291349
lsp_types::Documentation::MarkupContent(lsp_types::MarkupContent {
292350
kind: lsp_types::MarkupKind::Markdown,

crates/starpls_ide/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,10 @@ impl AnalysisSnapshot {
398398
self.query(|db| signature_help::signature_help(db, pos))
399399
}
400400

401+
pub fn file_contents(&self, file_id: FileId) -> Cancellable<Option<String>> {
402+
self.query(|db| db.get_file(file_id).map(|file| file.contents(db).clone()))
403+
}
404+
401405
/// Helper method to handle Salsa cancellations.
402406
fn query<'a, F, T>(&'a self, f: F) -> Cancellable<T>
403407
where

0 commit comments

Comments
 (0)