Skip to content

Commit

Permalink
Finish depends completions
Browse files Browse the repository at this point in the history
## What changed

- fix "is_cursor_within_node" function
- remove "textEdit" for depends completion (not needed for now)
- add tests for completions
- add tests for "is_cursor_within_node"
- add "get_commands" utility function
- add "get_current_command" utility function
- extract responses into its own module
  • Loading branch information
kindermax committed Jan 12, 2025
1 parent 96abb3b commit 55b7809
Show file tree
Hide file tree
Showing 4 changed files with 269 additions and 101 deletions.
105 changes: 71 additions & 34 deletions src/handler.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use lsp_server::{Notification, Request, RequestId};
use lsp_types::{CompletionParams, Position};
use lsp_types::CompletionParams;
use lsp_types::{
lsif::DefinitionResultType, request::GotoTypeDefinitionParams, DidChangeTextDocumentParams,
DidOpenTextDocumentParams, Location, Range,
};

use crate::state::State;
use crate::treesitter::{extract_filename, get_commands, get_position_type, is_mixin_root_node, word_after_cursor, word_before_cursor, Command, PositionType};
use crate::treesitter::{extract_filename, get_commands, get_current_command, get_position_type, Command, PositionType};

#[derive(Debug)]
pub struct DefinitionResult {
Expand All @@ -18,7 +18,7 @@ pub struct DefinitionResult {
pub struct LSPCompletion {
pub label: String,
pub details: Option<String>,
pub location: LSPLocation,
pub location: Option<LSPLocation>, // to support textEdit
}

#[derive(Debug, Default)]
Expand Down Expand Up @@ -76,11 +76,12 @@ pub fn handle_definition(req: Request, state: &mut State) -> Option<LspResult> {
let doc = state.get_document(uri)?;
let pos = params.text_document_position_params.position;

if !is_mixin_root_node(doc, &pos) {
if !matches!(get_position_type(doc, &pos), PositionType::Mixins) {
return None;
}

let filename = extract_filename(doc, &pos)?;
// TODO: check if file exists

let uri = go_to_def_filename(uri, &filename).parse().ok();
match uri {
Expand All @@ -97,22 +98,21 @@ pub fn handle_definition(req: Request, state: &mut State) -> Option<LspResult> {
}
}



pub fn handle_completion(req: Request, state: &mut State) -> Option<LspResult> {
let params: CompletionParams = serde_json::from_value(req.params).ok()?;
let uri = params.text_document_position.text_document.uri.as_str();
let doc = state.get_document(uri)?;

let position = params.text_document_position.position;
let line = doc.lines().nth(position.line as usize)?;

let items = match get_position_type(doc, position) {
let items = match get_position_type(doc, &position) {
PositionType::Depends => {
let commands = get_commands(doc);
on_completion_depends(&commands, uri, line, position).ok()?
let current_command = get_current_command(doc, &position)?;
on_completion_depends(&current_command, &commands).ok()?
},
PositionType::Mixins => {
on_completion_mixins().ok()?
},
// TODO: mixins
_ => return None,
};
Some(LspResult::Completion(CompletionResult {
Expand All @@ -121,36 +121,73 @@ pub fn handle_completion(req: Request, state: &mut State) -> Option<LspResult> {
}))
}

fn on_completion_depends(commands: &Vec<Command>, uri: &str, line: &str, position: Position) -> anyhow::Result<Vec<LSPCompletion>> {
let word = word_before_cursor(
line,
position.character as usize,
|c: char| c.is_whitespace(),
);
let after = word_after_cursor(line, position.character as usize, |c| {
c.is_whitespace()
});

fn on_completion_depends(current_command: &Command, commands: &[Command]) -> anyhow::Result<Vec<LSPCompletion>> {
commands
.iter()
// TODO: do not complete already added commands to depends list
.filter(|cmd| cmd.name != current_command.name)
.map(|cmd| -> anyhow::Result<LSPCompletion> {
Ok(LSPCompletion {
label: cmd.name.clone(),
details: None,
location: LSPLocation {
uri: uri.to_string(),
range: Range {
start: Position {
line: position.line,
character: position.character - u32::try_from(word.len())?,
},
end: Position {
line: position.line,
character: position.character + u32::try_from(after.len())?,
},
},
}
location: None,
})
})
.collect()
}

fn on_completion_mixins() -> anyhow::Result<Vec<LSPCompletion>> {
// walk current dir or take word as dir if with /
Ok(vec![])
}

#[cfg(test)]
mod tests {
use super::*;
use lsp_types::Position;

#[test]
fn test_complete_depends_block_sequence() {
let doc = r#"
shell: bash
commands:
test:
cmd: echo Test
depends:
-
test2:
cmd: echo Test2"#
.trim();

let position = Position::new(5, 7);
let commands = get_commands(doc);
let command = get_current_command(doc, &position).expect("Command not found");
let result = on_completion_depends(&command, &commands).expect("Completion failed");

assert_eq!(result.len(), 1);
assert_eq!(result[0].label, "test2");
assert!(result[0].location.is_none());
}

#[test]
fn test_complete_depends_flow_node() {
let doc = r#"
shell: bash
commands:
test:
cmd: echo Test
depends: []
test2:
cmd: echo Test2"#
.trim();

let position = Position::new(4, 14);
let commands = get_commands(doc);
let command = get_current_command(doc, &position).expect("Command not found");
let result = on_completion_depends(&command, &commands).expect("Completion failed");

assert_eq!(result.len(), 1);
assert_eq!(result[0].label, "test2");
assert!(result[0].location.is_none());
}
}
58 changes: 5 additions & 53 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ use lsp_types::ServerCapabilities;

use crate::handler::{handle_completion, handle_definition, handle_didChange, handle_didOpen, LspResult};
use crate::state::State;
use crate::responses::{completion_response, definition_response};

pub mod handler;
pub mod state;
pub mod treesitter;
pub mod responses;

fn main() -> Result<(), Box<dyn Error + Sync + Send>> {
eprintln!("Lets LSP server starting");
Expand All @@ -30,6 +32,7 @@ fn main() -> Result<(), Box<dyn Error + Sync + Send>> {
" ".to_string(),
"$".to_string(),
"- ".to_string(),
"[".to_string(),
]),
work_done_progress_options: lsp_types::WorkDoneProgressOptions {
work_done_progress: None,
Expand Down Expand Up @@ -80,16 +83,10 @@ fn main() -> Result<(), Box<dyn Error + Sync + Send>> {
match result {
LspResult::OK => (),
LspResult::Definition(result) => {
let result = lsp_server::Response {
id: result.id,
result: Some(serde_json::to_value(result.value)?),
error: None,
};
let response = Message::Response(result);
connection.sender.send(response)?
connection.sender.send(definition_response(result)?)?
},
LspResult::Completion(result) => {
connection.sender.send(completion(result))?
connection.sender.send(completion_response(result))?
}
}
}
Expand All @@ -101,48 +98,3 @@ fn main() -> Result<(), Box<dyn Error + Sync + Send>> {

Ok(())
}

fn completion(result: handler::CompletionResult) -> Message {
Message::Response(lsp_server::Response {
id: result.id,
result: serde_json::to_value(lsp_types::CompletionList {
items: result
.list
.iter()
.map(|c| {
let mut item = lsp_types::CompletionItem {
label: c.label.clone(),
kind: Some(lsp_types::CompletionItemKind::KEYWORD),
text_edit: Some(lsp_types::CompletionTextEdit::Edit(lsp_types::TextEdit {
new_text: c.label.clone(),
range: lsp_types::Range {
start: lsp_types::Position {
line: c.location.range.start.line,
character: c.location.range.start.character,
},
end: lsp_types::Position {
line: c.location.range.end.line,
character: c.location.range.end.character,
},
},
})),
..Default::default()
};

if let Some(documentation) = c.details.clone() {
item.documentation =
Some(lsp_types::Documentation::MarkupContent(lsp_types::MarkupContent {
kind: lsp_types::MarkupKind::Markdown,
value: documentation.clone(),
}));
}

item
})
.collect(),
is_incomplete: false,
})
.ok(),
error: None,
})
}
43 changes: 43 additions & 0 deletions src/responses.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use lsp_server::Message;

use crate::handler;

pub fn definition_response(result: handler::DefinitionResult) -> anyhow::Result<Message> {
Ok(Message::Response(lsp_server::Response {
id: result.id,
result: serde_json::to_value(result.value).ok(),
error: None,
}))
}

pub fn completion_response(result: handler::CompletionResult) -> Message {
Message::Response(lsp_server::Response {
id: result.id,
result: serde_json::to_value(lsp_types::CompletionList {
items: result
.list
.iter()
.map(|c| {
let mut item = lsp_types::CompletionItem {
label: c.label.clone(),
kind: Some(lsp_types::CompletionItemKind::KEYWORD),
..Default::default()
};

if let Some(documentation) = c.details.clone() {
item.documentation =
Some(lsp_types::Documentation::MarkupContent(lsp_types::MarkupContent {
kind: lsp_types::MarkupKind::Markdown,
value: documentation.clone(),
}));
}

item
})
.collect(),
is_incomplete: false,
})
.ok(),
error: None,
})
}
Loading

0 comments on commit 55b7809

Please sign in to comment.