mirror of
https://github.com/nushell/nushell
synced 2025-01-12 21:29:07 +00:00
feat(lsp): inlay hints of variable types and command params (#14802)
<!-- if this PR closes one or more issues, you can automatically link the PR with them by using one of the [*linking keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword), e.g. - this PR should close #xxxx - fixes #xxxx you can also mention related issues, PRs or discussions! --> # Description <!-- Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes. Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience. --> This PR adds inlay hints of variable types and parameter names to lsp-server <img width="547" alt="image" src="https://github.com/user-attachments/assets/07a0dd84-5ecc-47df-a8a7-732631715662" /> Some design choices I made: * for composite types like `record<foo: <record ...>>`, only a short name displayed. Full signature already available through `hover` * only parameter names of user defined commands are returned, feels too much distraction if enabled for all builtins * some information are lost in flattened expressions, so I implemented my AST traversing functions, which may seem unnecessary, but I can't find alternatives from the existing code. * another minor change: added a line separator to current hover markdown message. # User-Facing Changes <!-- List of all changes that impact the user experience here. This helps us keep track of breaking changes. --> Users who think this feature annoying now have to manually turn it off (or config the lsp client capabilities). # Tests + Formatting <!-- Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass (on Windows make sure to [enable developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging)) - `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the tests for the standard library > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` --> # After Submitting <!-- If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date. -->
This commit is contained in:
parent
902e6d7a27
commit
c811d86dbd
6 changed files with 560 additions and 49 deletions
|
@ -10,7 +10,8 @@ impl LanguageServer {
|
||||||
let mut engine_state = self.new_engine_state();
|
let mut engine_state = self.new_engine_state();
|
||||||
engine_state.generate_nu_constant();
|
engine_state.generate_nu_constant();
|
||||||
|
|
||||||
let Some((_, offset, working_set, file)) = self.parse_file(&mut engine_state, &uri) else {
|
let Some((_, offset, working_set, file)) = self.parse_file(&mut engine_state, &uri, true)
|
||||||
|
else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
472
crates/nu-lsp/src/hints.rs
Normal file
472
crates/nu-lsp/src/hints.rs
Normal file
|
@ -0,0 +1,472 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{span_to_range, LanguageServer};
|
||||||
|
use lsp_textdocument::FullTextDocument;
|
||||||
|
use lsp_types::{
|
||||||
|
InlayHint, InlayHintKind, InlayHintLabel, InlayHintParams, InlayHintTooltip, MarkupContent,
|
||||||
|
MarkupKind, Position, Range,
|
||||||
|
};
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::{
|
||||||
|
Argument, Block, Expr, Expression, ExternalArgument, ListItem, MatchPattern, Pattern,
|
||||||
|
PipelineRedirection, RecordItem,
|
||||||
|
},
|
||||||
|
engine::StateWorkingSet,
|
||||||
|
Type,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// similar to flatten_block, but allows extra map function
|
||||||
|
fn ast_flat_map<T, E>(
|
||||||
|
ast: &Arc<Block>,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
extra_args: &E,
|
||||||
|
f_special: fn(&Expression, &StateWorkingSet, &E) -> Option<Vec<T>>,
|
||||||
|
) -> Vec<T> {
|
||||||
|
ast.pipelines
|
||||||
|
.iter()
|
||||||
|
.flat_map(|pipeline| {
|
||||||
|
pipeline.elements.iter().flat_map(|element| {
|
||||||
|
expr_flat_map(&element.expr, working_set, extra_args, f_special)
|
||||||
|
.into_iter()
|
||||||
|
.chain(
|
||||||
|
element
|
||||||
|
.redirection
|
||||||
|
.as_ref()
|
||||||
|
.map(|redir| {
|
||||||
|
redirect_flat_map(redir, working_set, extra_args, f_special)
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// generic function that do flat_map on an expression
|
||||||
|
/// concats all recursive results on sub-expressions
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `f_special` - function that overrides the default behavior
|
||||||
|
fn expr_flat_map<T, E>(
|
||||||
|
expr: &Expression,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
extra_args: &E,
|
||||||
|
f_special: fn(&Expression, &StateWorkingSet, &E) -> Option<Vec<T>>,
|
||||||
|
) -> Vec<T> {
|
||||||
|
// behavior overridden by f_special
|
||||||
|
if let Some(vec) = f_special(expr, working_set, extra_args) {
|
||||||
|
return vec;
|
||||||
|
}
|
||||||
|
let recur = |expr| expr_flat_map(expr, working_set, extra_args, f_special);
|
||||||
|
match &expr.expr {
|
||||||
|
Expr::RowCondition(block_id)
|
||||||
|
| Expr::Subexpression(block_id)
|
||||||
|
| Expr::Block(block_id)
|
||||||
|
| Expr::Closure(block_id) => {
|
||||||
|
let block = working_set.get_block(block_id.to_owned());
|
||||||
|
ast_flat_map(block, working_set, extra_args, f_special)
|
||||||
|
}
|
||||||
|
Expr::Range(range) => [&range.from, &range.next, &range.to]
|
||||||
|
.iter()
|
||||||
|
.filter_map(|e| e.as_ref())
|
||||||
|
.flat_map(recur)
|
||||||
|
.collect(),
|
||||||
|
Expr::Call(call) => call
|
||||||
|
.arguments
|
||||||
|
.iter()
|
||||||
|
.filter_map(|arg| arg.expr())
|
||||||
|
.flat_map(recur)
|
||||||
|
.collect(),
|
||||||
|
Expr::ExternalCall(head, args) => recur(head)
|
||||||
|
.into_iter()
|
||||||
|
.chain(args.iter().flat_map(|arg| match arg {
|
||||||
|
ExternalArgument::Regular(e) | ExternalArgument::Spread(e) => recur(e),
|
||||||
|
}))
|
||||||
|
.collect(),
|
||||||
|
Expr::UnaryNot(expr) | Expr::Collect(_, expr) => recur(expr),
|
||||||
|
Expr::BinaryOp(lhs, op, rhs) => recur(lhs)
|
||||||
|
.into_iter()
|
||||||
|
.chain(recur(op))
|
||||||
|
.chain(recur(rhs))
|
||||||
|
.collect(),
|
||||||
|
Expr::MatchBlock(matches) => matches
|
||||||
|
.iter()
|
||||||
|
.flat_map(|(pattern, expr)| {
|
||||||
|
match_pattern_flat_map(pattern, working_set, extra_args, f_special)
|
||||||
|
.into_iter()
|
||||||
|
.chain(recur(expr))
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
Expr::List(items) => items
|
||||||
|
.iter()
|
||||||
|
.flat_map(|item| match item {
|
||||||
|
ListItem::Item(expr) | ListItem::Spread(_, expr) => recur(expr),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
Expr::Record(items) => items
|
||||||
|
.iter()
|
||||||
|
.flat_map(|item| match item {
|
||||||
|
RecordItem::Spread(_, expr) => recur(expr),
|
||||||
|
RecordItem::Pair(key, val) => [key, val].into_iter().flat_map(recur).collect(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
Expr::Table(table) => table
|
||||||
|
.columns
|
||||||
|
.iter()
|
||||||
|
.flat_map(recur)
|
||||||
|
.chain(table.rows.iter().flat_map(|row| row.iter().flat_map(recur)))
|
||||||
|
.collect(),
|
||||||
|
Expr::ValueWithUnit(vu) => recur(&vu.expr),
|
||||||
|
Expr::FullCellPath(fcp) => recur(&fcp.head),
|
||||||
|
Expr::Keyword(kw) => recur(&kw.expr),
|
||||||
|
Expr::StringInterpolation(vec) | Expr::GlobInterpolation(vec, _) => {
|
||||||
|
vec.iter().flat_map(recur).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// flat_map on match patterns
|
||||||
|
fn match_pattern_flat_map<T, E>(
|
||||||
|
pattern: &MatchPattern,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
extra_args: &E,
|
||||||
|
f_special: fn(&Expression, &StateWorkingSet, &E) -> Option<Vec<T>>,
|
||||||
|
) -> Vec<T> {
|
||||||
|
let recur = |expr| expr_flat_map(expr, working_set, extra_args, f_special);
|
||||||
|
let recur_match = |p| match_pattern_flat_map(p, working_set, extra_args, f_special);
|
||||||
|
match &pattern.pattern {
|
||||||
|
Pattern::Expression(expr) => recur(expr),
|
||||||
|
Pattern::List(patterns) | Pattern::Or(patterns) => {
|
||||||
|
patterns.iter().flat_map(recur_match).collect()
|
||||||
|
}
|
||||||
|
Pattern::Record(entries) => entries.iter().flat_map(|(_, p)| recur_match(p)).collect(),
|
||||||
|
_ => Vec::new(),
|
||||||
|
}
|
||||||
|
.into_iter()
|
||||||
|
.chain(pattern.guard.as_ref().map(|g| recur(g)).unwrap_or_default())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// flat_map on redirections
|
||||||
|
fn redirect_flat_map<T, E>(
|
||||||
|
redir: &PipelineRedirection,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
extra_args: &E,
|
||||||
|
f_special: fn(&Expression, &StateWorkingSet, &E) -> Option<Vec<T>>,
|
||||||
|
) -> Vec<T> {
|
||||||
|
let recur = |expr| expr_flat_map(expr, working_set, extra_args, f_special);
|
||||||
|
match redir {
|
||||||
|
PipelineRedirection::Single { target, .. } => target.expr().map(recur).unwrap_or_default(),
|
||||||
|
PipelineRedirection::Separate { out, err } => [out, err]
|
||||||
|
.iter()
|
||||||
|
.filter_map(|t| t.expr())
|
||||||
|
.flat_map(recur)
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_inlay_hints_from_expression(
|
||||||
|
expr: &Expression,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
extra_args: &(usize, &FullTextDocument),
|
||||||
|
) -> Option<Vec<InlayHint>> {
|
||||||
|
let span = expr.span;
|
||||||
|
let (offset, file) = extra_args;
|
||||||
|
let recur = |expr| {
|
||||||
|
expr_flat_map(
|
||||||
|
expr,
|
||||||
|
working_set,
|
||||||
|
extra_args,
|
||||||
|
extract_inlay_hints_from_expression,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
match &expr.expr {
|
||||||
|
Expr::VarDecl(var_id) => {
|
||||||
|
let position = span_to_range(&span, file, *offset).end;
|
||||||
|
// skip if the type is already specified in code
|
||||||
|
if file
|
||||||
|
.get_content(Some(Range {
|
||||||
|
start: position,
|
||||||
|
end: Position {
|
||||||
|
line: position.line,
|
||||||
|
character: position.character + 1,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
.contains(':')
|
||||||
|
{
|
||||||
|
return Some(Vec::new());
|
||||||
|
}
|
||||||
|
let var = working_set.get_variable(*var_id);
|
||||||
|
let type_str_short = match var.ty {
|
||||||
|
Type::Custom(_) => String::from("custom"),
|
||||||
|
Type::Record(_) => String::from("record"),
|
||||||
|
Type::Table(_) => String::from("table"),
|
||||||
|
Type::List(_) => String::from("list"),
|
||||||
|
_ => var.ty.to_string(),
|
||||||
|
};
|
||||||
|
Some(vec![
|
||||||
|
(InlayHint {
|
||||||
|
kind: Some(InlayHintKind::TYPE),
|
||||||
|
label: InlayHintLabel::String(format!(": {}", type_str_short)),
|
||||||
|
position,
|
||||||
|
text_edits: None,
|
||||||
|
tooltip: None,
|
||||||
|
data: None,
|
||||||
|
padding_left: None,
|
||||||
|
padding_right: None,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
Expr::Call(call) => {
|
||||||
|
let decl = working_set.get_decl(call.decl_id);
|
||||||
|
// skip those defined outside of the project
|
||||||
|
working_set.get_block(decl.block_id()?).span?;
|
||||||
|
let signatures = decl.signature();
|
||||||
|
let signatures = [
|
||||||
|
signatures.required_positional,
|
||||||
|
signatures.optional_positional,
|
||||||
|
]
|
||||||
|
.concat();
|
||||||
|
let arguments = &call.arguments;
|
||||||
|
let mut sig_idx = 0;
|
||||||
|
let mut hints = Vec::new();
|
||||||
|
for arg in arguments {
|
||||||
|
match arg {
|
||||||
|
// skip the rest when spread/unknown arguments encountered
|
||||||
|
Argument::Spread(expr) | Argument::Unknown(expr) => {
|
||||||
|
hints.extend(recur(expr));
|
||||||
|
sig_idx = signatures.len();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// skip current for flags
|
||||||
|
Argument::Named((_, _, Some(expr))) => {
|
||||||
|
hints.extend(recur(expr));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Argument::Positional(expr) => {
|
||||||
|
if let Some(sig) = signatures.get(sig_idx) {
|
||||||
|
sig_idx += 1;
|
||||||
|
let position = span_to_range(&arg.span(), file, *offset).start;
|
||||||
|
hints.push(InlayHint {
|
||||||
|
kind: Some(InlayHintKind::PARAMETER),
|
||||||
|
label: InlayHintLabel::String(format!("{}:", sig.name)),
|
||||||
|
position,
|
||||||
|
text_edits: None,
|
||||||
|
tooltip: Some(InlayHintTooltip::MarkupContent(MarkupContent {
|
||||||
|
kind: MarkupKind::Markdown,
|
||||||
|
value: format!("`{}: {}`", sig.shape, sig.desc),
|
||||||
|
})),
|
||||||
|
data: None,
|
||||||
|
padding_left: None,
|
||||||
|
padding_right: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
hints.extend(recur(expr));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(hints)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LanguageServer {
|
||||||
|
pub fn get_inlay_hints(&mut self, params: &InlayHintParams) -> Option<Vec<InlayHint>> {
|
||||||
|
Some(self.inlay_hints.get(¶ms.text_document.uri)?.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract_inlay_hints(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
block: &Arc<Block>,
|
||||||
|
offset: usize,
|
||||||
|
file: &FullTextDocument,
|
||||||
|
) -> Vec<InlayHint> {
|
||||||
|
ast_flat_map(
|
||||||
|
block,
|
||||||
|
working_set,
|
||||||
|
&(offset, file),
|
||||||
|
extract_inlay_hints_from_expression,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use assert_json_diff::assert_json_eq;
|
||||||
|
use lsp_types::request::Request;
|
||||||
|
use nu_test_support::fs::fixtures;
|
||||||
|
|
||||||
|
use crate::path_to_uri;
|
||||||
|
use crate::tests::{initialize_language_server, open_unchecked};
|
||||||
|
use lsp_server::{Connection, Message};
|
||||||
|
use lsp_types::{
|
||||||
|
request::InlayHintRequest, InlayHintParams, Position, Range, TextDocumentIdentifier, Uri,
|
||||||
|
WorkDoneProgressParams,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn send_inlay_hint_request(client_connection: &Connection, uri: Uri) -> Message {
|
||||||
|
client_connection
|
||||||
|
.sender
|
||||||
|
.send(Message::Request(lsp_server::Request {
|
||||||
|
id: 1.into(),
|
||||||
|
method: InlayHintRequest::METHOD.to_string(),
|
||||||
|
params: serde_json::to_value(InlayHintParams {
|
||||||
|
text_document: TextDocumentIdentifier { uri },
|
||||||
|
work_done_progress_params: WorkDoneProgressParams::default(),
|
||||||
|
// all inlay hints in the file are returned anyway
|
||||||
|
range: Range {
|
||||||
|
start: Position {
|
||||||
|
line: 0,
|
||||||
|
character: 0,
|
||||||
|
},
|
||||||
|
end: Position {
|
||||||
|
line: 0,
|
||||||
|
character: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
client_connection
|
||||||
|
.receiver
|
||||||
|
.recv_timeout(std::time::Duration::from_secs(2))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inlay_hint_variable_type() {
|
||||||
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
|
let mut script = fixtures();
|
||||||
|
script.push("lsp");
|
||||||
|
script.push("hints");
|
||||||
|
script.push("type.nu");
|
||||||
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
|
let resp = send_inlay_hint_request(&client_connection, script.clone());
|
||||||
|
let result = if let Message::Response(response) = resp {
|
||||||
|
response.result
|
||||||
|
} else {
|
||||||
|
panic!()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_json_eq!(
|
||||||
|
result,
|
||||||
|
serde_json::json!([
|
||||||
|
{
|
||||||
|
"position": { "line": 0, "character": 7 },
|
||||||
|
"label": ": int",
|
||||||
|
"kind": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"position": { "line": 1, "character": 7 },
|
||||||
|
"label": ": string",
|
||||||
|
"kind": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"position": { "line": 2, "character": 8 },
|
||||||
|
"label": ": bool",
|
||||||
|
"kind": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"position": { "line": 3, "character": 9 },
|
||||||
|
"label": ": float",
|
||||||
|
"kind": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"position": { "line": 4, "character": 8 },
|
||||||
|
"label": ": list",
|
||||||
|
"kind": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"position": { "line": 5, "character": 10 },
|
||||||
|
"label": ": record",
|
||||||
|
"kind": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"position": { "line": 6, "character": 11 },
|
||||||
|
"label": ": closure",
|
||||||
|
"kind": 1
|
||||||
|
}
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inlay_hint_parameter_names() {
|
||||||
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
|
let mut script = fixtures();
|
||||||
|
script.push("lsp");
|
||||||
|
script.push("hints");
|
||||||
|
script.push("param.nu");
|
||||||
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
|
let resp = send_inlay_hint_request(&client_connection, script.clone());
|
||||||
|
let result = if let Message::Response(response) = resp {
|
||||||
|
response.result
|
||||||
|
} else {
|
||||||
|
panic!()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_json_eq!(
|
||||||
|
result,
|
||||||
|
serde_json::json!([
|
||||||
|
{
|
||||||
|
"position": { "line": 9, "character": 9 },
|
||||||
|
"label": "a1:",
|
||||||
|
"kind": 2,
|
||||||
|
"tooltip": { "kind": "markdown", "value": "`any: `" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"position": { "line": 9, "character": 11 },
|
||||||
|
"label": "a2:",
|
||||||
|
"kind": 2,
|
||||||
|
"tooltip": { "kind": "markdown", "value": "`any: `" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"position": { "line": 9, "character": 18 },
|
||||||
|
"label": "a3:",
|
||||||
|
"kind": 2,
|
||||||
|
"tooltip": { "kind": "markdown", "value": "`any: arg3`" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"position": { "line": 10, "character": 6 },
|
||||||
|
"label": "a1:",
|
||||||
|
"kind": 2,
|
||||||
|
"tooltip": { "kind": "markdown", "value": "`any: `" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"position": { "line": 11, "character": 2 },
|
||||||
|
"label": "a2:",
|
||||||
|
"kind": 2,
|
||||||
|
"tooltip": { "kind": "markdown", "value": "`any: `" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"position": { "line": 12, "character": 11 },
|
||||||
|
"label": "a1:",
|
||||||
|
"kind": 2,
|
||||||
|
"tooltip": { "kind": "markdown", "value": "`any: `" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"position": { "line": 12, "character": 13 },
|
||||||
|
"label": "a2:",
|
||||||
|
"kind": 2,
|
||||||
|
"tooltip": { "kind": "markdown", "value": "`any: `" }
|
||||||
|
}
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,13 +3,13 @@ use lsp_server::{Connection, IoThreads, Message, Response, ResponseError};
|
||||||
use lsp_textdocument::{FullTextDocument, TextDocuments};
|
use lsp_textdocument::{FullTextDocument, TextDocuments};
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
request::{
|
request::{
|
||||||
Completion, DocumentSymbolRequest, GotoDefinition, HoverRequest, Request,
|
Completion, DocumentSymbolRequest, GotoDefinition, HoverRequest, InlayHintRequest, Request,
|
||||||
WorkspaceSymbolRequest,
|
WorkspaceSymbolRequest,
|
||||||
},
|
},
|
||||||
CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse, CompletionTextEdit,
|
CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse, CompletionTextEdit,
|
||||||
GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams, Location,
|
GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams, InlayHint,
|
||||||
MarkupContent, MarkupKind, OneOf, Range, ServerCapabilities, TextDocumentSyncKind, TextEdit,
|
Location, MarkupContent, MarkupKind, OneOf, Range, ServerCapabilities, TextDocumentSyncKind,
|
||||||
Uri,
|
TextEdit, Uri,
|
||||||
};
|
};
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use nu_cli::{NuCompleter, SuggestionKind};
|
use nu_cli::{NuCompleter, SuggestionKind};
|
||||||
|
@ -19,6 +19,7 @@ use nu_protocol::{
|
||||||
engine::{CachedFile, EngineState, Stack, StateWorkingSet},
|
engine::{CachedFile, EngineState, Stack, StateWorkingSet},
|
||||||
DeclId, ModuleId, Span, Value, VarId,
|
DeclId, ModuleId, Span, Value, VarId,
|
||||||
};
|
};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use std::{
|
use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
|
@ -29,6 +30,7 @@ use symbols::SymbolCache;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
mod diagnostics;
|
mod diagnostics;
|
||||||
|
mod hints;
|
||||||
mod notification;
|
mod notification;
|
||||||
mod symbols;
|
mod symbols;
|
||||||
|
|
||||||
|
@ -46,6 +48,7 @@ pub struct LanguageServer {
|
||||||
docs: TextDocuments,
|
docs: TextDocuments,
|
||||||
engine_state: EngineState,
|
engine_state: EngineState,
|
||||||
symbol_cache: SymbolCache,
|
symbol_cache: SymbolCache,
|
||||||
|
inlay_hints: BTreeMap<Uri, Vec<InlayHint>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn path_to_uri(path: impl AsRef<Path>) -> Uri {
|
pub fn path_to_uri(path: impl AsRef<Path>) -> Uri {
|
||||||
|
@ -87,6 +90,7 @@ impl LanguageServer {
|
||||||
docs: TextDocuments::new(),
|
docs: TextDocuments::new(),
|
||||||
engine_state,
|
engine_state,
|
||||||
symbol_cache: SymbolCache::new(),
|
symbol_cache: SymbolCache::new(),
|
||||||
|
inlay_hints: BTreeMap::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,6 +104,7 @@ impl LanguageServer {
|
||||||
completion_provider: Some(lsp_types::CompletionOptions::default()),
|
completion_provider: Some(lsp_types::CompletionOptions::default()),
|
||||||
document_symbol_provider: Some(OneOf::Left(true)),
|
document_symbol_provider: Some(OneOf::Left(true)),
|
||||||
workspace_symbol_provider: Some(OneOf::Left(true)),
|
workspace_symbol_provider: Some(OneOf::Left(true)),
|
||||||
|
inlay_hint_provider: Some(OneOf::Left(true)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.expect("Must be serializable");
|
.expect("Must be serializable");
|
||||||
|
@ -152,6 +157,9 @@ impl LanguageServer {
|
||||||
self.workspace_symbol(params)
|
self.workspace_symbol(params)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
InlayHintRequest::METHOD => {
|
||||||
|
Self::handle_lsp_request(request, |params| self.get_inlay_hints(params))
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -189,6 +197,7 @@ impl LanguageServer {
|
||||||
&mut self,
|
&mut self,
|
||||||
engine_state: &'a mut EngineState,
|
engine_state: &'a mut EngineState,
|
||||||
uri: &Uri,
|
uri: &Uri,
|
||||||
|
need_hints: bool,
|
||||||
) -> Option<(Arc<Block>, usize, StateWorkingSet<'a>, &FullTextDocument)> {
|
) -> Option<(Arc<Block>, usize, StateWorkingSet<'a>, &FullTextDocument)> {
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
let file = self.docs.get_document(uri)?;
|
let file = self.docs.get_document(uri)?;
|
||||||
|
@ -197,12 +206,14 @@ impl LanguageServer {
|
||||||
let contents = file.get_content(None).as_bytes();
|
let contents = file.get_content(None).as_bytes();
|
||||||
let _ = working_set.files.push(file_path.clone(), Span::unknown());
|
let _ = working_set.files.push(file_path.clone(), Span::unknown());
|
||||||
let block = parse(&mut working_set, Some(file_path_str), contents, false);
|
let block = parse(&mut working_set, Some(file_path_str), contents, false);
|
||||||
let offset = working_set
|
let offset = working_set.get_span_for_filename(file_path_str)?.start;
|
||||||
.get_span_for_filename(file_path_str)
|
|
||||||
.unwrap_or_else(|| panic!("Failed at get_span_for_filename {}", file_path_str))
|
|
||||||
.start;
|
|
||||||
// TODO: merge delta back to engine_state?
|
// TODO: merge delta back to engine_state?
|
||||||
// self.engine_state.merge_delta(working_set.render());
|
// self.engine_state.merge_delta(working_set.render());
|
||||||
|
|
||||||
|
if need_hints {
|
||||||
|
let file_inlay_hints = self.extract_inlay_hints(&working_set, &block, offset, file);
|
||||||
|
self.inlay_hints.insert(uri.clone(), file_inlay_hints);
|
||||||
|
}
|
||||||
Some((block, offset, working_set, file))
|
Some((block, offset, working_set, file))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,7 +312,7 @@ impl LanguageServer {
|
||||||
.uri
|
.uri
|
||||||
.to_owned();
|
.to_owned();
|
||||||
let (block, file_offset, working_set, file) =
|
let (block, file_offset, working_set, file) =
|
||||||
self.parse_file(&mut engine_state, &path_uri)?;
|
self.parse_file(&mut engine_state, &path_uri, false)?;
|
||||||
let flattened = flatten_block(&working_set, &block);
|
let flattened = flatten_block(&working_set, &block);
|
||||||
let (id, _, _) = Self::find_id(
|
let (id, _, _) = Self::find_id(
|
||||||
flattened,
|
flattened,
|
||||||
|
@ -338,7 +349,7 @@ impl LanguageServer {
|
||||||
.uri
|
.uri
|
||||||
.to_owned();
|
.to_owned();
|
||||||
let (block, file_offset, working_set, file) =
|
let (block, file_offset, working_set, file) =
|
||||||
self.parse_file(&mut engine_state, &path_uri)?;
|
self.parse_file(&mut engine_state, &path_uri, false)?;
|
||||||
let flattened = flatten_block(&working_set, &block);
|
let flattened = flatten_block(&working_set, &block);
|
||||||
let (id, _, _) = Self::find_id(
|
let (id, _, _) = Self::find_id(
|
||||||
flattened,
|
flattened,
|
||||||
|
@ -371,7 +382,7 @@ impl LanguageServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage
|
// Usage
|
||||||
description.push_str("### Usage \n```nu\n");
|
description.push_str("-----\n### Usage \n```nu\n");
|
||||||
let signature = decl.signature();
|
let signature = decl.signature();
|
||||||
description.push_str(&format!(" {}", signature.name));
|
description.push_str(&format!(" {}", signature.name));
|
||||||
if !signature.named.is_empty() {
|
if !signature.named.is_empty() {
|
||||||
|
@ -803,7 +814,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn goto_definition(
|
fn send_goto_definition_request(
|
||||||
client_connection: &Connection,
|
client_connection: &Connection,
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
line: u32,
|
line: u32,
|
||||||
|
@ -844,7 +855,7 @@ mod tests {
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
let resp = goto_definition(&client_connection, script.clone(), 2, 12);
|
let resp = send_goto_definition_request(&client_connection, script.clone(), 2, 12);
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
|
@ -856,9 +867,9 @@ mod tests {
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"uri": script,
|
"uri": script,
|
||||||
"range": {
|
"range": {
|
||||||
"start": { "line": 0, "character": 4 },
|
"start": { "line": 0, "character": 4 },
|
||||||
"end": { "line": 0, "character": 12 }
|
"end": { "line": 0, "character": 12 }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -875,7 +886,7 @@ mod tests {
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
let resp = goto_definition(&client_connection, script.clone(), 4, 1);
|
let resp = send_goto_definition_request(&client_connection, script.clone(), 4, 1);
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
|
@ -906,7 +917,7 @@ mod tests {
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
let resp = goto_definition(&client_connection, script.clone(), 4, 2);
|
let resp = send_goto_definition_request(&client_connection, script.clone(), 4, 2);
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
|
@ -937,7 +948,7 @@ mod tests {
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
let resp = goto_definition(&client_connection, script.clone(), 1, 14);
|
let resp = send_goto_definition_request(&client_connection, script.clone(), 1, 14);
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
|
@ -956,7 +967,12 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hover(client_connection: &Connection, uri: Uri, line: u32, character: u32) -> Message {
|
pub fn send_hover_request(
|
||||||
|
client_connection: &Connection,
|
||||||
|
uri: Uri,
|
||||||
|
line: u32,
|
||||||
|
character: u32,
|
||||||
|
) -> Message {
|
||||||
client_connection
|
client_connection
|
||||||
.sender
|
.sender
|
||||||
.send(Message::Request(lsp_server::Request {
|
.send(Message::Request(lsp_server::Request {
|
||||||
|
@ -991,7 +1007,7 @@ mod tests {
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
let resp = hover(&client_connection, script.clone(), 2, 0);
|
let resp = send_hover_request(&client_connection, script.clone(), 2, 0);
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
|
@ -1018,7 +1034,7 @@ mod tests {
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
let resp = hover(&client_connection, script.clone(), 3, 0);
|
let resp = send_hover_request(&client_connection, script.clone(), 3, 0);
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
|
@ -1030,7 +1046,7 @@ mod tests {
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"contents": {
|
"contents": {
|
||||||
"kind": "markdown",
|
"kind": "markdown",
|
||||||
"value": "Renders some greeting message\n### Usage \n```nu\n hello {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n"
|
"value": "Renders some greeting message\n-----\n### Usage \n```nu\n hello {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -1048,7 +1064,7 @@ mod tests {
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
let resp = hover(&client_connection, script.clone(), 5, 8);
|
let resp = send_hover_request(&client_connection, script.clone(), 5, 8);
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
|
@ -1060,13 +1076,18 @@ mod tests {
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"contents": {
|
"contents": {
|
||||||
"kind": "markdown",
|
"kind": "markdown",
|
||||||
"value": "Concatenate multiple strings into a single string, with an optional separator between each.\n### Usage \n```nu\n str join {flags} <separator?>\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n\n### Parameters\n\n `separator: string` - Optional separator to use when creating string.\n\n\n### Input/output types\n\n```nu\n list<any> | string\n string | string\n\n```\n### Example(s)\n Create a string from input\n```nu\n ['nu', 'shell'] | str join\n```\n Create a string from input with a separator\n```nu\n ['nu', 'shell'] | str join '-'\n```\n"
|
"value": "Concatenate multiple strings into a single string, with an optional separator between each.\n-----\n### Usage \n```nu\n str join {flags} <separator?>\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n\n### Parameters\n\n `separator: string` - Optional separator to use when creating string.\n\n\n### Input/output types\n\n```nu\n list<any> | string\n string | string\n\n```\n### Example(s)\n Create a string from input\n```nu\n ['nu', 'shell'] | str join\n```\n Create a string from input with a separator\n```nu\n ['nu', 'shell'] | str join '-'\n```\n"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete(client_connection: &Connection, uri: Uri, line: u32, character: u32) -> Message {
|
fn send_complete_request(
|
||||||
|
client_connection: &Connection,
|
||||||
|
uri: Uri,
|
||||||
|
line: u32,
|
||||||
|
character: u32,
|
||||||
|
) -> Message {
|
||||||
client_connection
|
client_connection
|
||||||
.sender
|
.sender
|
||||||
.send(Message::Request(lsp_server::Request {
|
.send(Message::Request(lsp_server::Request {
|
||||||
|
@ -1103,7 +1124,7 @@ mod tests {
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
let resp = complete(&client_connection, script, 2, 9);
|
let resp = send_complete_request(&client_connection, script, 2, 9);
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
|
@ -1140,7 +1161,7 @@ mod tests {
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
let resp = complete(&client_connection, script, 0, 8);
|
let resp = send_complete_request(&client_connection, script, 0, 8);
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
|
@ -1178,7 +1199,7 @@ mod tests {
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
let resp = complete(&client_connection, script, 0, 13);
|
let resp = send_complete_request(&client_connection, script, 0, 13);
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
|
@ -1216,7 +1237,7 @@ mod tests {
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
let resp = complete(&client_connection, script, 0, 2);
|
let resp = send_complete_request(&client_connection, script, 0, 2);
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
notification::{
|
notification::{
|
||||||
DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument, DidSaveTextDocument,
|
DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument, Notification,
|
||||||
Notification,
|
|
||||||
},
|
},
|
||||||
DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
|
DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, Uri,
|
||||||
DidSaveTextDocumentParams, Uri,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::LanguageServer;
|
use crate::LanguageServer;
|
||||||
|
@ -23,12 +21,6 @@ impl LanguageServer {
|
||||||
.expect("Expect receive DidOpenTextDocumentParams");
|
.expect("Expect receive DidOpenTextDocumentParams");
|
||||||
Some(params.text_document.uri)
|
Some(params.text_document.uri)
|
||||||
}
|
}
|
||||||
DidSaveTextDocument::METHOD => {
|
|
||||||
let params: DidSaveTextDocumentParams =
|
|
||||||
serde_json::from_value(notification.params.clone())
|
|
||||||
.expect("Expect receive DidSaveTextDocumentParams");
|
|
||||||
Some(params.text_document.uri)
|
|
||||||
}
|
|
||||||
DidChangeTextDocument::METHOD => {
|
DidChangeTextDocument::METHOD => {
|
||||||
let params: DidChangeTextDocumentParams =
|
let params: DidChangeTextDocumentParams =
|
||||||
serde_json::from_value(notification.params.clone())
|
serde_json::from_value(notification.params.clone())
|
||||||
|
@ -39,7 +31,9 @@ impl LanguageServer {
|
||||||
let params: DidCloseTextDocumentParams =
|
let params: DidCloseTextDocumentParams =
|
||||||
serde_json::from_value(notification.params.clone())
|
serde_json::from_value(notification.params.clone())
|
||||||
.expect("Expect receive DidCloseTextDocumentParams");
|
.expect("Expect receive DidCloseTextDocumentParams");
|
||||||
self.symbol_cache.drop(¶ms.text_document.uri);
|
let uri = params.text_document.uri;
|
||||||
|
self.symbol_cache.drop(&uri);
|
||||||
|
self.inlay_hints.remove(&uri);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
|
@ -55,7 +49,9 @@ mod tests {
|
||||||
use nu_test_support::fs::fixtures;
|
use nu_test_support::fs::fixtures;
|
||||||
|
|
||||||
use crate::path_to_uri;
|
use crate::path_to_uri;
|
||||||
use crate::tests::{hover, initialize_language_server, open, open_unchecked, update};
|
use crate::tests::{
|
||||||
|
initialize_language_server, open, open_unchecked, send_hover_request, update,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hover_correct_documentation_on_let() {
|
fn hover_correct_documentation_on_let() {
|
||||||
|
@ -69,7 +65,7 @@ mod tests {
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
let resp = hover(&client_connection, script.clone(), 0, 0);
|
let resp = send_hover_request(&client_connection, script.clone(), 0, 0);
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
|
@ -81,7 +77,7 @@ mod tests {
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"contents": {
|
"contents": {
|
||||||
"kind": "markdown",
|
"kind": "markdown",
|
||||||
"value": "Create a variable and give it a value.\n\nThis command is a parser keyword. For details, check:\n https://www.nushell.sh/book/thinking_in_nu.html\n### Usage \n```nu\n let {flags} <var_name> <initial_value>\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n\n### Parameters\n\n `var_name: any` - Variable name.\n\n `initial_value: any` - Equals sign followed by value.\n\n\n### Input/output types\n\n```nu\n any | nothing\n\n```\n### Example(s)\n Set a variable to a value\n```nu\n let x = 10\n```\n Set a variable to the result of an expression\n```nu\n let x = 10 + 100\n```\n Set a variable based on the condition\n```nu\n let x = if false { -1 } else { 1 }\n```\n"
|
"value": "Create a variable and give it a value.\n\nThis command is a parser keyword. For details, check:\n https://www.nushell.sh/book/thinking_in_nu.html\n-----\n### Usage \n```nu\n let {flags} <var_name> <initial_value>\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n\n### Parameters\n\n `var_name: any` - Variable name.\n\n `initial_value: any` - Equals sign followed by value.\n\n\n### Input/output types\n\n```nu\n any | nothing\n\n```\n### Example(s)\n Set a variable to a value\n```nu\n let x = 10\n```\n Set a variable to the result of an expression\n```nu\n let x = 10 + 100\n```\n Set a variable based on the condition\n```nu\n let x = if false { -1 } else { 1 }\n```\n"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -110,7 +106,7 @@ hello"#,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
let resp = hover(&client_connection, script.clone(), 3, 0);
|
let resp = send_hover_request(&client_connection, script.clone(), 3, 0);
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
|
@ -122,7 +118,7 @@ hello"#,
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"contents": {
|
"contents": {
|
||||||
"kind": "markdown",
|
"kind": "markdown",
|
||||||
"value": "Renders some updated greeting message\n### Usage \n```nu\n hello {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n"
|
"value": "Renders some updated greeting message\n-----\n### Usage \n```nu\n hello {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -155,7 +151,7 @@ hello"#,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
let resp = hover(&client_connection, script.clone(), 3, 0);
|
let resp = send_hover_request(&client_connection, script.clone(), 3, 0);
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
|
@ -167,7 +163,7 @@ hello"#,
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"contents": {
|
"contents": {
|
||||||
"kind": "markdown",
|
"kind": "markdown",
|
||||||
"value": "Renders some updated greeting message\n### Usage \n```nu\n hello {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n"
|
"value": "Renders some updated greeting message\n-----\n### Usage \n```nu\n hello {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
13
tests/fixtures/lsp/hints/param.nu
vendored
Normal file
13
tests/fixtures/lsp/hints/param.nu
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
def cmd [
|
||||||
|
a1
|
||||||
|
a2
|
||||||
|
--flag (-f)
|
||||||
|
a3? # arg3
|
||||||
|
a4?
|
||||||
|
...arg_rest
|
||||||
|
] { }
|
||||||
|
|
||||||
|
ls | cmd 1 $nu -f (
|
||||||
|
cmd 1
|
||||||
|
2
|
||||||
|
) ...[(cmd 1 2)]
|
8
tests/fixtures/lsp/hints/type.nu
vendored
Normal file
8
tests/fixtures/lsp/hints/type.nu
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
let int = 1
|
||||||
|
let str = "hello"
|
||||||
|
let bool = true
|
||||||
|
let float = 1.0
|
||||||
|
let list = [1 2 3]
|
||||||
|
let record = {}
|
||||||
|
let closure = {|x| $x }
|
||||||
|
let ignored: int = 5
|
Loading…
Reference in a new issue