Add hover

This commit is contained in:
Ayaz Hafiz 2022-08-20 16:24:11 -05:00
parent 9d365a8a57
commit 0db1cd9c28
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
6 changed files with 193 additions and 19 deletions

View file

@ -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(

View file

@ -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>,
} }

View file

@ -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"] }

View file

@ -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,

View file

@ -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)
}
} }

View file

@ -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]