mirror of
https://github.com/oxalica/nil.git
synced 2024-11-23 12:03:30 +03:00
Impl hover for names
This commit is contained in:
parent
fbe5d4d974
commit
f94b668d25
187
crates/ide/src/ide/hover.rs
Normal file
187
crates/ide/src/ide/hover.rs
Normal file
@ -0,0 +1,187 @@
|
||||
use crate::def::{AstPtr, Expr, ResolveResult};
|
||||
use crate::{DefDatabase, FilePos, NameKind};
|
||||
use builtin::ALL_BUILTINS;
|
||||
use rowan::ast::AstNode;
|
||||
use rowan::TextRange;
|
||||
use std::fmt::Write;
|
||||
use syntax::{ast, best_token_at_offset, match_ast};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct HoverResult {
|
||||
pub range: TextRange,
|
||||
pub markup: String,
|
||||
}
|
||||
|
||||
pub(crate) fn hover(
|
||||
db: &dyn DefDatabase,
|
||||
FilePos { file_id, pos }: FilePos,
|
||||
) -> Option<HoverResult> {
|
||||
let parse = db.parse(file_id);
|
||||
let tok = best_token_at_offset(&parse.syntax_node(), pos)?;
|
||||
let ptr = tok.parent_ancestors().find_map(|node| {
|
||||
match_ast! {
|
||||
match node {
|
||||
ast::Ref(n) => Some(AstPtr::new(n.syntax())),
|
||||
ast::Name(n) => Some(AstPtr::new(n.syntax())),
|
||||
ast::Literal(n) => Some(AstPtr::new(n.syntax())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
})?;
|
||||
let range = ptr.text_range();
|
||||
|
||||
let src = db.file_content(file_id);
|
||||
let module = db.module(file_id);
|
||||
let source_map = db.source_map(file_id);
|
||||
let nameres = db.name_resolution(file_id);
|
||||
|
||||
let mut name = None;
|
||||
|
||||
if let Some(expr) = source_map.expr_for_node(ptr.clone()) {
|
||||
match nameres.get(expr) {
|
||||
None => {}
|
||||
Some(ResolveResult::Builtin(name)) => {
|
||||
let b = &ALL_BUILTINS[*name];
|
||||
let mut markup = String::new();
|
||||
match b.summary {
|
||||
None => writeln!(markup, "`builtins.{name}`").unwrap(),
|
||||
Some(summary) => writeln!(markup, "`{summary}`").unwrap(),
|
||||
}
|
||||
if let Some(doc) = b.doc {
|
||||
writeln!(markup, "\n{doc}").unwrap();
|
||||
}
|
||||
return Some(HoverResult { range, markup });
|
||||
}
|
||||
Some(ResolveResult::WithExprs(withs)) => {
|
||||
let text = match &module[expr] {
|
||||
Expr::Reference(text) => text,
|
||||
_ => return None,
|
||||
};
|
||||
let mut markup = format!("Attribute `{}` from:", text);
|
||||
for (&expr, i) in withs.iter().zip(1..) {
|
||||
let ptr = source_map.node_for_expr(expr)?;
|
||||
let with_node = ast::With::cast(ptr.to_node(&parse.syntax_node()))?;
|
||||
let env_text = with_node
|
||||
.environment()
|
||||
.map_or("?", |env_node| &src[env_node.syntax().text_range()]);
|
||||
write!(markup, "\n{i}. `with {env_text};`").unwrap();
|
||||
}
|
||||
return Some(HoverResult { range, markup });
|
||||
}
|
||||
Some(ResolveResult::Definition(def)) => {
|
||||
name = Some(*def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(name) = name.or_else(|| source_map.name_for_node(ptr.clone())) {
|
||||
let text = &module[name].text;
|
||||
let kind = match module[name].kind {
|
||||
NameKind::LetIn => "Let binding",
|
||||
NameKind::PlainAttrset => "Attrset attribute",
|
||||
NameKind::RecAttrset => "Rec-attrset attribute",
|
||||
NameKind::Param => "Parameter",
|
||||
NameKind::PatField => "Field parameter",
|
||||
};
|
||||
return Some(HoverResult {
|
||||
range,
|
||||
markup: format!("{kind} `{text}`"),
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::base::SourceDatabase;
|
||||
use crate::tests::TestDB;
|
||||
use expect_test::{expect, Expect};
|
||||
|
||||
#[track_caller]
|
||||
fn check(fixture: &str, full: &str, expect: Expect) {
|
||||
let (db, f) = TestDB::from_fixture(fixture).unwrap();
|
||||
assert_eq!(f.markers().len(), 1);
|
||||
let ret = super::hover(&db, f[0]).expect("No hover");
|
||||
let src = db.file_content(f[0].file_id);
|
||||
assert_eq!(full, &src[ret.range]);
|
||||
let mut got = ret.markup.trim().to_string();
|
||||
if got.contains('\n') {
|
||||
got += "\n";
|
||||
}
|
||||
expect.assert_eq(&got);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn definition() {
|
||||
check("let $0a = 1; in a", "a", expect!["Let binding `a`"]);
|
||||
check("let a.$0a = 1; in a", "a", expect!["Attrset attribute `a`"]);
|
||||
check("{ $0a = 1; }", "a", expect!["Attrset attribute `a`"]);
|
||||
check(
|
||||
"rec { $0a = 1; }",
|
||||
"a",
|
||||
expect!["Rec-attrset attribute `a`"],
|
||||
);
|
||||
check("$0a: a", "a", expect!["Parameter `a`"]);
|
||||
check("{$0a}: a", "a", expect!["Field parameter `a`"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reference() {
|
||||
check("let a = 1; in $0a", "a", expect!["Let binding `a`"]);
|
||||
check(
|
||||
"let a = 1; in { inherit $0a; }",
|
||||
"a",
|
||||
expect!["Let binding `a`"],
|
||||
);
|
||||
check(
|
||||
"let a = 1; in rec { inherit $0a; }",
|
||||
"a",
|
||||
expect!["Let binding `a`"],
|
||||
);
|
||||
check("a: $0a", "a", expect!["Parameter `a`"]);
|
||||
check("{a}: $0a", "a", expect!["Field parameter `a`"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with() {
|
||||
check(
|
||||
"with 1; $0a",
|
||||
"a",
|
||||
expect![[r#"
|
||||
Attribute `a` from:
|
||||
1. `with 1;`
|
||||
"#]],
|
||||
);
|
||||
check(
|
||||
"with 1; with 2; $0a",
|
||||
"a",
|
||||
expect![[r#"
|
||||
Attribute `a` from:
|
||||
1. `with 2;`
|
||||
2. `with 1;`
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin() {
|
||||
check("$0true", "true", expect!["`builtins.true`"]);
|
||||
check(
|
||||
"$0map",
|
||||
"map",
|
||||
expect![[r#"
|
||||
`builtins.map f list`
|
||||
|
||||
Apply the function *f* to each element in the list *list*. For
|
||||
example,
|
||||
|
||||
```nix
|
||||
map (x: "foo" + x) [ "bar" "bla" "abc" ]
|
||||
```
|
||||
|
||||
evaluates to `[ "foobar" "foobla" "fooabc" ]`.
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ mod completion;
|
||||
mod diagnostics;
|
||||
mod expand_selection;
|
||||
mod goto_definition;
|
||||
mod hover;
|
||||
mod references;
|
||||
mod rename;
|
||||
mod syntax_highlighting;
|
||||
@ -15,6 +16,7 @@ use smol_str::SmolStr;
|
||||
use std::fmt;
|
||||
|
||||
pub use completion::{CompletionItem, CompletionItemKind};
|
||||
pub use hover::HoverResult;
|
||||
pub use syntax_highlighting::{HlKeyword, HlOperator, HlPunct, HlRange, HlTag};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@ -129,4 +131,8 @@ impl Analysis {
|
||||
) -> Cancellable<RenameResult<WorkspaceEdit>> {
|
||||
self.with_db(|db| rename::rename(db, fpos, new_name))
|
||||
}
|
||||
|
||||
pub fn hover(&self, fpos: FilePos) -> Cancellable<Option<HoverResult>> {
|
||||
self.with_db(|db| hover::hover(db, fpos))
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ mod tests;
|
||||
|
||||
pub use self::ide::{
|
||||
Analysis, AnalysisHost, Cancelled, CompletionItem, CompletionItemKind, HlKeyword, HlOperator,
|
||||
HlPunct, HlRange, HlTag, NavigationTarget, RootDatabase,
|
||||
HlPunct, HlRange, HlTag, HoverResult, NavigationTarget, RootDatabase,
|
||||
};
|
||||
pub use base::{
|
||||
Change, FileId, FilePos, FileRange, FileSet, InFile, SourceDatabase, SourceRoot, SourceRootId,
|
||||
|
@ -1,9 +1,10 @@
|
||||
use crate::semantic_tokens::{SEMANTIC_TOKEN_MODIFIERS, SEMANTIC_TOKEN_TYPES};
|
||||
use lsp_types::{
|
||||
CompletionOptions, OneOf, RenameOptions, SelectionRangeProviderCapability,
|
||||
SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions,
|
||||
SemanticTokensServerCapabilities, ServerCapabilities, TextDocumentSyncCapability,
|
||||
TextDocumentSyncKind, TextDocumentSyncOptions, WorkDoneProgressOptions,
|
||||
CompletionOptions, HoverProviderCapability, OneOf, RenameOptions,
|
||||
SelectionRangeProviderCapability, SemanticTokensFullOptions, SemanticTokensLegend,
|
||||
SemanticTokensOptions, SemanticTokensServerCapabilities, ServerCapabilities,
|
||||
TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
|
||||
WorkDoneProgressOptions,
|
||||
};
|
||||
|
||||
pub(crate) fn server_capabilities() -> ServerCapabilities {
|
||||
@ -39,6 +40,7 @@ pub(crate) fn server_capabilities() -> ServerCapabilities {
|
||||
full: Some(SemanticTokensFullOptions::Delta { delta: Some(false) }),
|
||||
},
|
||||
)),
|
||||
hover_provider: Some(HoverProviderCapability::Simple(true)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,15 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{semantic_tokens, LineMap, LspError, Result, Vfs};
|
||||
use ide::{
|
||||
CompletionItem, CompletionItemKind, Diagnostic, FileId, FilePos, FileRange, HlRange, Severity,
|
||||
TextEdit, WorkspaceEdit,
|
||||
CompletionItem, CompletionItemKind, Diagnostic, FileId, FilePos, FileRange, HlRange,
|
||||
HoverResult, Severity, TextEdit, WorkspaceEdit,
|
||||
};
|
||||
use lsp_server::ErrorCode;
|
||||
use lsp_types::{
|
||||
self as lsp, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Documentation,
|
||||
Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse, Range, SemanticToken,
|
||||
TextDocumentIdentifier, TextDocumentPositionParams,
|
||||
Hover, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse, Range,
|
||||
SemanticToken, TextDocumentIdentifier, TextDocumentPositionParams,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use text_size::{TextRange, TextSize};
|
||||
|
||||
pub(crate) fn from_file(vfs: &Vfs, doc: &TextDocumentIdentifier) -> Result<FileId> {
|
||||
@ -268,3 +267,13 @@ pub(crate) fn to_semantic_tokens(line_map: &LineMap, hls: &[HlRange]) -> Vec<Sem
|
||||
|
||||
toks
|
||||
}
|
||||
|
||||
pub(crate) fn to_hover(line_map: &LineMap, hover: HoverResult) -> Hover {
|
||||
Hover {
|
||||
range: Some(to_range(line_map, hover.range)),
|
||||
contents: lsp::HoverContents::Markup(MarkupContent {
|
||||
kind: MarkupKind::Markdown,
|
||||
value: hover.markup,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::{convert, Result, StateSnapshot};
|
||||
use ide::FileRange;
|
||||
use lsp_types::{
|
||||
CompletionParams, CompletionResponse, GotoDefinitionParams, GotoDefinitionResponse, Location,
|
||||
PrepareRenameResponse, ReferenceParams, RenameParams, SelectionRange, SelectionRangeParams,
|
||||
SemanticTokens, SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult,
|
||||
SemanticTokensResult, TextDocumentPositionParams, WorkspaceEdit,
|
||||
CompletionParams, CompletionResponse, GotoDefinitionParams, GotoDefinitionResponse, Hover,
|
||||
HoverParams, Location, PrepareRenameResponse, ReferenceParams, RenameParams, SelectionRange,
|
||||
SelectionRangeParams, SemanticTokens, SemanticTokensParams, SemanticTokensRangeParams,
|
||||
SemanticTokensRangeResult, SemanticTokensResult, TextDocumentPositionParams, WorkspaceEdit,
|
||||
};
|
||||
use text_size::TextRange;
|
||||
|
||||
@ -145,3 +145,10 @@ pub(crate) fn semantic_token_range(
|
||||
data: toks,
|
||||
})))
|
||||
}
|
||||
|
||||
pub(crate) fn hover(snap: StateSnapshot, params: HoverParams) -> Result<Option<Hover>> {
|
||||
let (line_map, fpos) =
|
||||
convert::from_file_pos(&snap.vfs(), ¶ms.text_document_position_params)?;
|
||||
let ret = snap.analysis.hover(fpos)?;
|
||||
Ok(ret.map(|hover| convert::to_hover(&line_map, hover)))
|
||||
}
|
||||
|
@ -121,6 +121,7 @@ impl State {
|
||||
.on::<req::Rename>(handler::rename)
|
||||
.on::<req::SemanticTokensFullRequest>(handler::semantic_token_full)
|
||||
.on::<req::SemanticTokensRangeRequest>(handler::semantic_token_range)
|
||||
.on::<req::HoverRequest>(handler::hover)
|
||||
.finish();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user