Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lsp/inlay hints on pipe #3290

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions compiler-core/src/ast/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,35 @@ pub enum TypedExpr {
}

impl TypedExpr {
/// Determines if the expression is a simple literal whose inlayHints must not be showed
/// in a pipeline chain
pub fn is_simple_lit(&self) -> bool {
match self {
TypedExpr::Int { .. }
| TypedExpr::Float { .. }
| TypedExpr::String { .. }
| TypedExpr::BitArray { .. } => true,
TypedExpr::Block { .. }
| TypedExpr::Pipeline { .. }
| TypedExpr::Var { .. }
| TypedExpr::Fn { .. }
| TypedExpr::List { .. }
| TypedExpr::Call { .. }
| TypedExpr::BinOp { .. }
| TypedExpr::Case { .. }
| TypedExpr::RecordAccess { .. }
| TypedExpr::ModuleSelect { .. }
| TypedExpr::Tuple { .. }
| TypedExpr::TupleIndex { .. }
| TypedExpr::Todo { .. }
| TypedExpr::Panic { .. }
| TypedExpr::RecordUpdate { .. }
| TypedExpr::NegateBool { .. }
| TypedExpr::NegateInt { .. }
| TypedExpr::Invalid { .. } => false,
}
}

pub fn is_println(&self) -> bool {
let fun = match self {
TypedExpr::Call { fun, args, .. } if args.len() == 1 => fun.as_ref(),
Expand Down
14 changes: 9 additions & 5 deletions compiler-core/src/language_server.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
mod code_action;
mod compiler;
mod completer;
mod configuration;
mod edits;
mod engine;
mod feedback;
mod files;
mod inlay_hints;
mod messages;
mod progress;
mod router;
Expand Down Expand Up @@ -39,13 +41,15 @@ pub trait DownloadDependencies {
fn download_dependencies(&self, paths: &ProjectPaths) -> Result<Manifest>;
}

pub fn src_span_to_lsp_range(location: SrcSpan, line_numbers: &LineNumbers) -> Range {
let start = line_numbers.line_and_column_number(location.start);
let end = line_numbers.line_and_column_number(location.end);
pub fn src_offset_to_lsp_position(offset: u32, line_numbers: &LineNumbers) -> Position {
let line_col = line_numbers.line_and_column_number(offset);
Position::new(line_col.line - 1, line_col.column - 1)
}

pub fn src_span_to_lsp_range(location: SrcSpan, line_numbers: &LineNumbers) -> Range {
Range::new(
Position::new(start.line - 1, start.column - 1),
Position::new(end.line - 1, end.column - 1),
src_offset_to_lsp_position(location.start, line_numbers),
src_offset_to_lsp_position(location.end, line_numbers),
)
}

Expand Down
17 changes: 17 additions & 0 deletions compiler-core/src/language_server/configuration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use serde::Deserialize;
use std::sync::{Arc, RwLock};

pub type SharedConfig = Arc<RwLock<Configuration>>;

#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq)]
#[serde(default)]
pub struct Configuration {
pub inlay_hints: InlayHintsConfig,
}

#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq)]
#[serde(default)]
pub struct InlayHintsConfig {
/// Whether to show type inlay hints of multiline pipelines
pub pipelines: bool,
}
32 changes: 30 additions & 2 deletions compiler-core/src/language_server/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use ecow::EcoString;
use itertools::Itertools;
use lsp::CodeAction;
use lsp_types::{
self as lsp, DocumentSymbol, Hover, HoverContents, MarkedString, Position, Range,
self as lsp, DocumentSymbol, Hover, HoverContents, InlayHint, MarkedString, Position, Range,
SignatureHelp, SymbolKind, SymbolTag, TextEdit, Url,
};
use std::sync::Arc;
Expand All @@ -38,7 +38,8 @@ use super::{
TurnIntoUse,
},
completer::Completer,
signature_help, src_span_to_lsp_range, DownloadDependencies, MakeLocker,
configuration::SharedConfig,
inlay_hints, signature_help, src_span_to_lsp_range, DownloadDependencies, MakeLocker,
};

#[derive(Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -77,6 +78,9 @@ pub struct LanguageServerEngine<IO, Reporter> {
/// Used to know if to show the "View on HexDocs" link
/// when hovering on an imported value
hex_deps: std::collections::HashSet<EcoString>,

/// Configuration the user has set in their editor.
pub(crate) user_config: SharedConfig,
}

impl<'a, IO, Reporter> LanguageServerEngine<IO, Reporter>
Expand All @@ -97,6 +101,7 @@ where
progress_reporter: Reporter,
io: FileSystemProxy<IO>,
paths: ProjectPaths,
user_config: SharedConfig,
) -> Result<Self> {
let locker = io.inner().make_locker(&paths, config.target)?;

Expand Down Expand Up @@ -133,6 +138,7 @@ where
paths,
error: None,
hex_deps,
user_config,
})
}

Expand Down Expand Up @@ -493,6 +499,28 @@ where
})
}

pub fn inlay_hints(&mut self, params: lsp::InlayHintParams) -> Response<Vec<InlayHint>> {
self.respond(|this| {
let Ok(config) = this.user_config.read() else {
return Ok(vec![]);
};

if !config.inlay_hints.pipelines {
return Ok(vec![]);
}

let Some(module) = this.module_for_uri(&params.text_document.uri) else {
return Ok(vec![]);
};

let line_numbers = LineNumbers::new(&module.code);

let hints = inlay_hints::get_inlay_hints(module.ast.clone(), &line_numbers);

Ok(hints)
})
}

fn respond<T>(&mut self, handler: impl FnOnce(&mut Self) -> Result<T>) -> Response<T> {
let result = handler(self);
let warnings = self.take_warnings();
Expand Down
109 changes: 109 additions & 0 deletions compiler-core/src/language_server/inlay_hints.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use lsp_types::{InlayHint, InlayHintKind, InlayHintLabel};

use crate::{
ast::{
visit::{self, Visit},
SrcSpan, TypedAssignment, TypedExpr, TypedModule,
},
line_numbers::LineNumbers,
type_::pretty::Printer,
};

use super::src_offset_to_lsp_position;

struct InlayHintsVisitor<'a> {
hints: Vec<InlayHint>,
line_numbers: &'a LineNumbers,
}

impl<'a> InlayHintsVisitor<'a> {
fn new(line_numbers: &'a LineNumbers) -> InlayHintsVisitor<'a> {
InlayHintsVisitor {
hints: vec![],
line_numbers,
}
}
}

fn default_inlay_hint(line_numbers: &LineNumbers, offset: u32, label: String) -> InlayHint {
let position = src_offset_to_lsp_position(offset, line_numbers);

InlayHint {
position,
label: InlayHintLabel::String(label),
kind: Some(InlayHintKind::TYPE),
text_edits: None,
tooltip: None,
padding_left: Some(true),
padding_right: None,
data: None,
}
}

impl<'ast> Visit<'ast> for InlayHintsVisitor<'_> {
fn visit_typed_expr_pipeline(
&mut self,
_location: &'ast SrcSpan,
assignments: &'ast [TypedAssignment],

Check failure on line 47 in compiler-core/src/language_server/inlay_hints.rs

View workflow job for this annotation

GitHub Actions / test (stable, x86_64-unknown-linux-gnu)

method `visit_typed_expr_pipeline` has an incompatible type for trait

Check failure on line 47 in compiler-core/src/language_server/inlay_hints.rs

View workflow job for this annotation

GitHub Actions / test (stable, x86_64-unknown-linux-musl)

method `visit_typed_expr_pipeline` has an incompatible type for trait

Check failure on line 47 in compiler-core/src/language_server/inlay_hints.rs

View workflow job for this annotation

GitHub Actions / test (stable, aarch64-unknown-linux-gnu)

method `visit_typed_expr_pipeline` has an incompatible type for trait

Check failure on line 47 in compiler-core/src/language_server/inlay_hints.rs

View workflow job for this annotation

GitHub Actions / test (stable, aarch64-unknown-linux-musl)

method `visit_typed_expr_pipeline` has an incompatible type for trait

Check failure on line 47 in compiler-core/src/language_server/inlay_hints.rs

View workflow job for this annotation

GitHub Actions / test (stable, x86_64-apple-darwin)

method `visit_typed_expr_pipeline` has an incompatible type for trait

Check failure on line 47 in compiler-core/src/language_server/inlay_hints.rs

View workflow job for this annotation

GitHub Actions / test (stable, x86_64-pc-windows-msvc)

method `visit_typed_expr_pipeline` has an incompatible type for trait

Check failure on line 47 in compiler-core/src/language_server/inlay_hints.rs

View workflow job for this annotation

GitHub Actions / test (stable, aarch64-apple-darwin, macos-latest, arm64, false, true)

method `visit_typed_expr_pipeline` has an incompatible type for trait
finally: &'ast TypedExpr,
) {
let mut prev_hint: Option<(u32, Option<InlayHint>)> = None;
for assign in assignments {
let this_line: u32 = self
.line_numbers
.line_and_column_number(assign.location.end)
.line;

if let Some((prev_line, prev_hint)) = prev_hint {
if prev_line != this_line {
if let Some(prev_hint) = prev_hint {
self.hints.push(prev_hint);
}
}
};

let this_hint = default_inlay_hint(
self.line_numbers,
assign.location.end,
Printer::new().pretty_print(assign.type_().as_ref(), 0),
);

prev_hint = Some((
this_line,
if assign.value.is_simple_lit() {
None
} else {
Some(this_hint)
},
));

visit::visit_typed_expr(self, &assign.value);
}

if let Some((prev_line, prev_hint)) = prev_hint {
let this_line = self
.line_numbers
.line_and_column_number(finally.location().end)
.line;
if this_line != prev_line {
if let Some(prev_hint) = prev_hint {
self.hints.push(prev_hint);
}
let hint = default_inlay_hint(
self.line_numbers,
finally.location().end,
Printer::new().pretty_print(finally.type_().as_ref(), 0),
);
self.hints.push(hint);
}
}

visit::visit_typed_expr(self, finally);
}
}

pub fn get_inlay_hints(typed_module: TypedModule, line_numbers: &LineNumbers) -> Vec<InlayHint> {
let mut visitor = InlayHintsVisitor::new(line_numbers);
visitor.visit_typed_module(&typed_module);
visitor.hints
}
Loading
Loading