mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 03:42:17 +00:00
Add hover
This commit is contained in:
parent
9d365a8a57
commit
0db1cd9c28
6 changed files with 193 additions and 19 deletions
|
@ -1,7 +1,7 @@
|
||||||
//! Traversals over the can ast.
|
//! Traversals over the can ast.
|
||||||
|
|
||||||
use roc_module::{ident::Lowercase, symbol::Symbol};
|
use roc_module::{ident::Lowercase, symbol::Symbol};
|
||||||
use roc_region::all::{Loc, Region};
|
use roc_region::all::{Loc, Position, Region};
|
||||||
use roc_types::{subs::Variable, types::MemberImpl};
|
use roc_types::{subs::Variable, types::MemberImpl};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -657,6 +657,35 @@ impl Visitor for TypeAtVisitor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct TypeAtPositionVisitor {
|
||||||
|
position: Position,
|
||||||
|
region_typ: Option<(Region, Variable)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Visitor for TypeAtPositionVisitor {
|
||||||
|
fn should_visit(&mut self, region: Region) -> bool {
|
||||||
|
region.contains_pos(self.position)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_expr(&mut self, expr: &Expr, region: Region, var: Variable) {
|
||||||
|
if region.contains_pos(self.position) {
|
||||||
|
self.region_typ = Some((region, var));
|
||||||
|
|
||||||
|
walk_expr(self, expr, var);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_pattern(&mut self, pat: &Pattern, region: Region, opt_var: Option<Variable>) {
|
||||||
|
if region.contains_pos(self.position) {
|
||||||
|
if let Some(var) = opt_var {
|
||||||
|
self.region_typ = Some((region, var));
|
||||||
|
}
|
||||||
|
|
||||||
|
walk_pattern(self, pat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempts to find the type of an expression at `region`, if it exists.
|
/// Attempts to find the type of an expression at `region`, if it exists.
|
||||||
pub fn find_type_at(region: Region, decls: &Declarations) -> Option<Variable> {
|
pub fn find_type_at(region: Region, decls: &Declarations) -> Option<Variable> {
|
||||||
let mut visitor = TypeAtVisitor { region, typ: None };
|
let mut visitor = TypeAtVisitor { region, typ: None };
|
||||||
|
@ -675,6 +704,20 @@ pub enum FoundSymbol {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Given an ability Foo implements foo : ..., returns (T, foo1) if the symbol at the given region is a
|
/// Given an ability Foo implements foo : ..., returns (T, foo1) if the symbol at the given region is a
|
||||||
|
/// Like [find_type_at], but descends into the narrowest node containing [position].
|
||||||
|
pub fn find_closest_type_at(
|
||||||
|
position: Position,
|
||||||
|
decls: &Declarations,
|
||||||
|
) -> Option<(Region, Variable)> {
|
||||||
|
let mut visitor = TypeAtPositionVisitor {
|
||||||
|
position,
|
||||||
|
region_typ: None,
|
||||||
|
};
|
||||||
|
visitor.visit_decls(decls);
|
||||||
|
visitor.region_typ
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given an ability Foo has foo : ..., returns (T, foo1) if the symbol at the given region is a
|
||||||
/// symbol foo1 that specializes foo for T. Otherwise if the symbol is foo but the specialization
|
/// symbol foo1 that specializes foo for T. Otherwise if the symbol is foo but the specialization
|
||||||
/// is unknown, (Foo, foo) is returned. Otherwise [None] is returned.
|
/// is unknown, (Foo, foo) is returned. Otherwise [None] is returned.
|
||||||
pub fn find_symbol_at(
|
pub fn find_symbol_at(
|
||||||
|
|
|
@ -22,6 +22,10 @@ impl Region {
|
||||||
self.start <= other.start && self.end >= other.end
|
self.start <= other.start && self.end >= other.end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn contains_pos(&self, pos: Position) -> bool {
|
||||||
|
self.start <= pos && self.end >= pos
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.start == self.end
|
self.start == self.end
|
||||||
}
|
}
|
||||||
|
@ -356,6 +360,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct LineInfo {
|
pub struct LineInfo {
|
||||||
line_offsets: Vec<u32>,
|
line_offsets: Vec<u32>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,14 @@ name = "roc_ls"
|
||||||
path = "src/server.rs"
|
path = "src/server.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
roc_can = { path = "../compiler/can" }
|
||||||
roc_load = { path = "../compiler/load" }
|
roc_load = { path = "../compiler/load" }
|
||||||
roc_problem = { path = "../compiler/problem" }
|
roc_problem = { path = "../compiler/problem" }
|
||||||
roc_region = { path = "../compiler/region" }
|
roc_region = { path = "../compiler/region" }
|
||||||
roc_reporting = { path = "../reporting" }
|
roc_reporting = { path = "../reporting" }
|
||||||
roc_solve_problem = { path = "../compiler/solve_problem" }
|
roc_solve_problem = { path = "../compiler/solve_problem" }
|
||||||
roc_target = { path = "../compiler/roc_target" }
|
roc_target = { path = "../compiler/roc_target" }
|
||||||
|
roc_types = { path = "../compiler/types" }
|
||||||
|
|
||||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,70 @@
|
||||||
use roc_region::all::{LineColumnRegion, LineInfo, Region};
|
use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Region};
|
||||||
use tower_lsp::lsp_types::{Position, Range};
|
use tower_lsp::lsp_types::{Position, Range};
|
||||||
|
|
||||||
fn range_of_region(line_info: &LineInfo, region: Region) -> Range {
|
pub(crate) trait ToRange {
|
||||||
let LineColumnRegion { start, end } = line_info.convert_region(region);
|
type Feed;
|
||||||
Range {
|
|
||||||
start: Position {
|
fn to_range(&self, feed: &Self::Feed) -> Range;
|
||||||
line: start.line,
|
}
|
||||||
character: start.column,
|
|
||||||
},
|
impl ToRange for Region {
|
||||||
end: Position {
|
type Feed = LineInfo;
|
||||||
line: end.line,
|
|
||||||
character: end.column,
|
fn to_range(&self, line_info: &LineInfo) -> Range {
|
||||||
},
|
let LineColumnRegion { start, end } = line_info.convert_region(*self);
|
||||||
|
Range {
|
||||||
|
start: Position {
|
||||||
|
line: start.line,
|
||||||
|
character: start.column,
|
||||||
|
},
|
||||||
|
end: Position {
|
||||||
|
line: end.line,
|
||||||
|
character: end.column,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait ToRegion {
|
||||||
|
type Feed;
|
||||||
|
|
||||||
|
fn to_region(&self, feed: &Self::Feed) -> Region;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToRegion for Range {
|
||||||
|
type Feed = LineInfo;
|
||||||
|
|
||||||
|
fn to_region(&self, line_info: &LineInfo) -> Region {
|
||||||
|
let lc_region = LineColumnRegion {
|
||||||
|
start: LineColumn {
|
||||||
|
line: self.start.line,
|
||||||
|
column: self.start.character,
|
||||||
|
},
|
||||||
|
end: LineColumn {
|
||||||
|
line: self.end.line,
|
||||||
|
column: self.end.line,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
line_info.convert_line_column_region(lc_region)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait ToRocPosition {
|
||||||
|
type Feed;
|
||||||
|
|
||||||
|
fn to_roc_position(&self, feed: &Self::Feed) -> roc_region::all::Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToRocPosition for tower_lsp::lsp_types::Position {
|
||||||
|
type Feed = LineInfo;
|
||||||
|
|
||||||
|
fn to_roc_position(&self, line_info: &LineInfo) -> roc_region::all::Position {
|
||||||
|
let lc = LineColumn {
|
||||||
|
line: self.line,
|
||||||
|
column: self.character,
|
||||||
|
};
|
||||||
|
line_info.convert_line_column(lc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +78,7 @@ pub(crate) mod diag {
|
||||||
use roc_reporting::report::{RocDocAllocator, Severity};
|
use roc_reporting::report::{RocDocAllocator, Severity};
|
||||||
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range};
|
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range};
|
||||||
|
|
||||||
use super::range_of_region;
|
use super::ToRange;
|
||||||
|
|
||||||
pub trait IntoLspSeverity {
|
pub trait IntoLspSeverity {
|
||||||
fn into_lsp_severity(self) -> DiagnosticSeverity;
|
fn into_lsp_severity(self) -> DiagnosticSeverity;
|
||||||
|
@ -114,7 +167,7 @@ pub(crate) mod diag {
|
||||||
type Feed = ProblemFmt<'a>;
|
type Feed = ProblemFmt<'a>;
|
||||||
|
|
||||||
fn into_lsp_diagnostic(self, fmt: &'a ProblemFmt<'a>) -> Option<Diagnostic> {
|
fn into_lsp_diagnostic(self, fmt: &'a ProblemFmt<'a>) -> Option<Diagnostic> {
|
||||||
let range = range_of_region(fmt.line_info, self.region());
|
let range = self.region().to_range(fmt.line_info);
|
||||||
|
|
||||||
let report = roc_reporting::report::can_problem(
|
let report = roc_reporting::report::can_problem(
|
||||||
&fmt.alloc,
|
&fmt.alloc,
|
||||||
|
@ -146,7 +199,7 @@ pub(crate) mod diag {
|
||||||
type Feed = ProblemFmt<'a>;
|
type Feed = ProblemFmt<'a>;
|
||||||
|
|
||||||
fn into_lsp_diagnostic(self, fmt: &'a ProblemFmt<'a>) -> Option<Diagnostic> {
|
fn into_lsp_diagnostic(self, fmt: &'a ProblemFmt<'a>) -> Option<Diagnostic> {
|
||||||
let range = range_of_region(fmt.line_info, self.region());
|
let range = self.region().to_range(fmt.line_info);
|
||||||
|
|
||||||
let report = roc_reporting::report::type_problem(
|
let report = roc_reporting::report::type_problem(
|
||||||
&fmt.alloc,
|
&fmt.alloc,
|
||||||
|
|
|
@ -4,9 +4,12 @@ use bumpalo::Bump;
|
||||||
use roc_load::{LoadedModule, LoadingProblem};
|
use roc_load::{LoadedModule, LoadingProblem};
|
||||||
use roc_region::all::LineInfo;
|
use roc_region::all::LineInfo;
|
||||||
use roc_reporting::report::RocDocAllocator;
|
use roc_reporting::report::RocDocAllocator;
|
||||||
use tower_lsp::lsp_types::{Diagnostic, Url};
|
use tower_lsp::lsp_types::{Diagnostic, Hover, HoverContents, MarkedString, Position, Url};
|
||||||
|
|
||||||
use crate::convert::diag::{IntoLspDiagnostic, ProblemFmt};
|
use crate::convert::{
|
||||||
|
diag::{IntoLspDiagnostic, ProblemFmt},
|
||||||
|
ToRange, ToRocPosition,
|
||||||
|
};
|
||||||
|
|
||||||
pub(crate) enum DocumentChange {
|
pub(crate) enum DocumentChange {
|
||||||
Modified(Url, String),
|
Modified(Url, String),
|
||||||
|
@ -21,6 +24,7 @@ struct Document {
|
||||||
arena: Bump,
|
arena: Bump,
|
||||||
|
|
||||||
// Incrementally updated module, diagnostis, etc.
|
// Incrementally updated module, diagnostis, etc.
|
||||||
|
line_info: Option<LineInfo>,
|
||||||
module: Option<Result<LoadedModule, ()>>,
|
module: Option<Result<LoadedModule, ()>>,
|
||||||
diagnostics: Option<Vec<Diagnostic>>,
|
diagnostics: Option<Vec<Diagnostic>>,
|
||||||
}
|
}
|
||||||
|
@ -32,6 +36,7 @@ impl Document {
|
||||||
source,
|
source,
|
||||||
arena: Bump::new(),
|
arena: Bump::new(),
|
||||||
|
|
||||||
|
line_info: None,
|
||||||
module: None,
|
module: None,
|
||||||
diagnostics: None,
|
diagnostics: None,
|
||||||
}
|
}
|
||||||
|
@ -43,6 +48,16 @@ impl Document {
|
||||||
self.diagnostics = None;
|
self.diagnostics = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prime_line_info(&mut self) {
|
||||||
|
if self.line_info.is_none() {
|
||||||
|
self.line_info = Some(LineInfo::new(&self.source));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_info(&self) -> &LineInfo {
|
||||||
|
&self.line_info.as_ref().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
fn module(&mut self) -> Result<&mut LoadedModule, LoadingProblem<'_>> {
|
fn module(&mut self) -> Result<&mut LoadedModule, LoadingProblem<'_>> {
|
||||||
if let Some(Ok(module)) = &mut self.module {
|
if let Some(Ok(module)) = &mut self.module {
|
||||||
// Safety: returning for time self is alive
|
// Safety: returning for time self is alive
|
||||||
|
@ -84,8 +99,11 @@ impl Document {
|
||||||
|
|
||||||
let diagnostics = match loaded {
|
let diagnostics = match loaded {
|
||||||
Ok(module) => {
|
Ok(module) => {
|
||||||
|
let line_info = {
|
||||||
|
self.prime_line_info();
|
||||||
|
self.line_info()
|
||||||
|
};
|
||||||
let lines: Vec<_> = self.source.lines().collect();
|
let lines: Vec<_> = self.source.lines().collect();
|
||||||
let line_info = LineInfo::new(&self.source);
|
|
||||||
|
|
||||||
let alloc = RocDocAllocator::new(&lines, module.module_id, &module.interns);
|
let alloc = RocDocAllocator::new(&lines, module.module_id, &module.interns);
|
||||||
|
|
||||||
|
@ -129,6 +147,41 @@ impl Document {
|
||||||
self.diagnostics = Some(diagnostics);
|
self.diagnostics = Some(diagnostics);
|
||||||
self.diagnostics.as_ref().unwrap().clone()
|
self.diagnostics.as_ref().unwrap().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hover(&mut self, position: Position) -> Option<Hover> {
|
||||||
|
let line_info = {
|
||||||
|
self.prime_line_info();
|
||||||
|
self.line_info()
|
||||||
|
};
|
||||||
|
|
||||||
|
let position = position.to_roc_position(line_info);
|
||||||
|
|
||||||
|
let module = match self.module() {
|
||||||
|
Ok(module) => module,
|
||||||
|
Err(_) => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let decls = module.declarations_by_id.get(&module.module_id).unwrap();
|
||||||
|
let (region, var) = roc_can::traverse::find_closest_type_at(position, decls)?;
|
||||||
|
|
||||||
|
let subs = module.solved.inner_mut();
|
||||||
|
let snapshot = subs.snapshot();
|
||||||
|
let type_str = roc_types::pretty_print::name_and_print_var(
|
||||||
|
var,
|
||||||
|
subs,
|
||||||
|
module.module_id,
|
||||||
|
&module.interns,
|
||||||
|
roc_types::pretty_print::DebugPrint::NOTHING,
|
||||||
|
);
|
||||||
|
subs.rollback_to(snapshot);
|
||||||
|
|
||||||
|
let range = region.to_range(self.line_info());
|
||||||
|
|
||||||
|
Some(Hover {
|
||||||
|
contents: HoverContents::Scalar(MarkedString::String(type_str)),
|
||||||
|
range: Some(range),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
|
@ -155,4 +208,8 @@ impl Registry {
|
||||||
pub fn diagnostics(&mut self, document: &Url) -> Vec<Diagnostic> {
|
pub fn diagnostics(&mut self, document: &Url) -> Vec<Diagnostic> {
|
||||||
self.documents.get_mut(document).unwrap().diagnostics()
|
self.documents.get_mut(document).unwrap().diagnostics()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn hover(&mut self, document: &Url, position: Position) -> Option<Hover> {
|
||||||
|
self.documents.get_mut(document).unwrap().hover(position)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,6 +100,20 @@ impl LanguageServer for RocLs {
|
||||||
async fn shutdown(&self) -> Result<()> {
|
async fn shutdown(&self) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
|
||||||
|
let HoverParams {
|
||||||
|
text_document_position_params:
|
||||||
|
TextDocumentPositionParams {
|
||||||
|
text_document,
|
||||||
|
position,
|
||||||
|
},
|
||||||
|
work_done_progress_params: _,
|
||||||
|
} = params;
|
||||||
|
|
||||||
|
let hover = self.registry().hover(&text_document.uri, position);
|
||||||
|
Ok(hover)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue