Skip to content

Commit 8ad61bb

Browse files
committed
feat: Stylua LSP server
1 parent 7c07c54 commit 8ad61bb

File tree

5 files changed

+180
-14
lines changed

5 files changed

+180
-14
lines changed

Cargo.lock

Lines changed: 62 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ thiserror = "1.0.49"
5656
threadpool = "1.8.1"
5757
toml = "0.8.1"
5858

59+
lsp-server = "0.7"
60+
lsp-types = "0.97"
61+
5962
[target.'cfg(target_arch = "wasm32")'.dependencies]
6063
wasm-bindgen = { version = "0.2.81", optional = true }
6164

src/cli/lsp.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
use lsp_server::{Connection, Message, RequestId, Response};
2+
use lsp_types::{
3+
request::{Formatting, RangeFormatting, Request},
4+
DocumentFormattingParams, InitializeParams, InitializeResult, OneOf, Position, Range,
5+
ServerCapabilities, TextEdit,
6+
};
7+
8+
use crate::{config::ConfigResolver, opt};
9+
10+
pub fn run(opt: opt::Opt) -> anyhow::Result<()> {
11+
// Load the configuration
12+
let opt_for_config_resolver = opt.clone();
13+
let mut config_resolver = ConfigResolver::new(&opt_for_config_resolver)?;
14+
15+
let (connection, io_threads) = Connection::stdio();
16+
17+
// Wait for initialize request
18+
let (id, params) = connection.initialize_start()?;
19+
let _params = serde_json::from_value::<InitializeParams>(params)?;
20+
21+
let capabilities = ServerCapabilities {
22+
document_formatting_provider: Some(OneOf::Left(true)),
23+
..Default::default()
24+
};
25+
26+
let init_result = InitializeResult {
27+
capabilities,
28+
..Default::default()
29+
};
30+
31+
connection.initialize_finish(id, serde_json::to_value(init_result)?)?;
32+
33+
for msg in &connection.receiver {
34+
match msg {
35+
Message::Request(req) => {
36+
if connection.handle_shutdown(&req)? {
37+
break;
38+
}
39+
40+
match req.method.as_str() {
41+
Formatting::METHOD => {
42+
let result =
43+
match serde_json::from_value::<DocumentFormattingParams>(req.params) {
44+
Ok(params) => {
45+
let res = handle_formatting(
46+
req.id.clone(),
47+
params,
48+
&mut config_resolver,
49+
);
50+
51+
res.unwrap_or(Response::new_ok(req.id, serde_json::Value::Null))
52+
}
53+
Err(err) => Response::new_err(req.id, 1, err.to_string()),
54+
};
55+
56+
connection.sender.send(Message::Response(result))?;
57+
}
58+
RangeFormatting::METHOD => {
59+
// TODO: Would be cool to support this in the future
60+
}
61+
_ => {}
62+
}
63+
}
64+
Message::Response(_) => {}
65+
Message::Notification(_) => {}
66+
}
67+
}
68+
69+
io_threads.join()?;
70+
71+
Ok(())
72+
}
73+
74+
fn handle_formatting(
75+
id: RequestId,
76+
params: DocumentFormattingParams,
77+
config_resolver: &mut ConfigResolver,
78+
) -> Option<Response> {
79+
let path = params.text_document.uri.path().as_str();
80+
let src = std::fs::read_to_string(path).ok()?;
81+
82+
let config = config_resolver
83+
.load_configuration(path.as_ref())
84+
.unwrap_or_default();
85+
86+
let new_text =
87+
stylua_lib::format_code(&src, config, None, stylua_lib::OutputVerification::None).ok()?;
88+
89+
if new_text == src {
90+
return None;
91+
}
92+
93+
// TODO: We can be smarter about this in the future, and update only the parts that changed
94+
let edit = TextEdit {
95+
range: Range {
96+
start: Position::new(0, 0),
97+
end: Position::new(u32::MAX, 0),
98+
},
99+
new_text,
100+
};
101+
102+
let edits: Vec<TextEdit> = vec![edit];
103+
104+
Some(Response::new_ok(id, edits))
105+
}

src/cli/main.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use stylua_lib::{format_code, Config, OutputVerification, Range};
1919
use crate::config::find_ignore_file_path;
2020

2121
mod config;
22+
mod lsp;
2223
mod opt;
2324
mod output_diff;
2425

@@ -255,6 +256,11 @@ fn path_is_stylua_ignored(path: &Path, search_parent_directories: bool) -> Resul
255256
fn format(opt: opt::Opt) -> Result<i32> {
256257
debug!("resolved options: {:#?}", opt);
257258

259+
if opt.lsp {
260+
lsp::run(opt)?;
261+
return Ok(0);
262+
}
263+
258264
if opt.files.is_empty() {
259265
bail!("no files provided");
260266
}

src/cli/opt.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ pub struct Opt {
108108
/// Respect .styluaignore and glob matching for file paths provided directly to the tool
109109
#[structopt(long)]
110110
pub respect_ignores: bool,
111+
112+
/// Run Stylua LSP server
113+
#[structopt(long)]
114+
pub lsp: bool,
111115
}
112116

113117
#[derive(ArgEnum, Clone, Copy, Debug, PartialEq, Eq)]

0 commit comments

Comments
 (0)