Skip to content

Commit

Permalink
feat(lsp): support all official position encodings
Browse files Browse the repository at this point in the history
Until now the server always assumed UTF-16, as that is the default in
Visual Studio Code, but other editors like Neovim might ask for a
different encoding.

This now allows to run the LSP server with any of the official encodings
UTF-8, UTF-16 and UTF-32.
  • Loading branch information
dnaka91 committed Feb 14, 2024
1 parent 83bfd8c commit e6d4884
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 205 deletions.
52 changes: 17 additions & 35 deletions crates/mabo-lsp/src/handlers/compile.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::ops::Range;

use line_index::{LineIndex, TextSize, WideEncoding};
use lsp_types::{self as lsp, Diagnostic, Url};
use mabo_compiler::validate;
use mabo_parser::{
Expand All @@ -13,11 +12,9 @@ use mabo_parser::{
Schema,
};

pub fn compile<'a>(
file: Url,
schema: &'a str,
index: &'_ LineIndex,
) -> Result<Schema<'a>, Diagnostic> {
use super::index::Index;

pub fn compile<'a>(file: Url, schema: &'a str, index: &'_ Index) -> Result<Schema<'a>, Diagnostic> {
let parsed =
mabo_parser::Schema::parse(schema, None).map_err(|e| parse_schema_diagnostic(index, &e))?;

Expand All @@ -33,7 +30,7 @@ pub fn simplify<'a>(
result.as_ref().map(mabo_compiler::simplify_schema)
}

fn parse_schema_diagnostic(index: &LineIndex, e: &ParseSchemaError) -> Diagnostic {
fn parse_schema_diagnostic(index: &Index, e: &ParseSchemaError) -> Diagnostic {
match &e.cause {
ParseSchemaCause::Parser(_, at) => {
Diagnostic::new_simple(get_range(index, *at..*at), e.to_string())
Expand All @@ -43,7 +40,7 @@ fn parse_schema_diagnostic(index: &LineIndex, e: &ParseSchemaError) -> Diagnosti
}
}

fn parse_definition_diagnostic(index: &LineIndex, e: &ParseDefinitionError) -> Diagnostic {
fn parse_definition_diagnostic(index: &Index, e: &ParseDefinitionError) -> Diagnostic {
match e {
ParseDefinitionError::Parser(_, at) => {
Diagnostic::new_simple(get_range(index, *at..*at), e.to_string())
Expand Down Expand Up @@ -128,7 +125,7 @@ fn parse_definition_diagnostic(index: &LineIndex, e: &ParseDefinitionError) -> D
}
}

fn parse_type_diagnostic(index: &LineIndex, e: &ParseTypeError) -> Diagnostic {
fn parse_type_diagnostic(index: &Index, e: &ParseTypeError) -> Diagnostic {
match &e.cause {
ParseTypeCause::Parser(_, at) => {
Diagnostic::new_simple(get_range(index, *at..*at), e.to_string())
Expand All @@ -138,11 +135,11 @@ fn parse_type_diagnostic(index: &LineIndex, e: &ParseTypeError) -> Diagnostic {
}
}

fn parse_comment_diagnostic(index: &LineIndex, e: &ParseCommentError) -> Diagnostic {
fn parse_comment_diagnostic(index: &Index, e: &ParseCommentError) -> Diagnostic {
Diagnostic::new_simple(get_range(index, e.at.clone()), e.to_string())
}

fn parse_import_cause_diagnostic(index: &LineIndex, c: &ParseImportCause) -> Diagnostic {
fn parse_import_cause_diagnostic(index: &Index, c: &ParseImportCause) -> Diagnostic {
match c {
ParseImportCause::Parser(_, at) | ParseImportCause::InvalidSegmentName { at } => {
Diagnostic::new_simple(get_range(index, *at..*at), c.to_string())
Expand All @@ -168,7 +165,7 @@ fn parse_import_cause_diagnostic(index: &LineIndex, c: &ParseImportCause) -> Dia
}
}

fn parse_generics_diagnostic(index: &LineIndex, e: &ParseGenericsError) -> Diagnostic {
fn parse_generics_diagnostic(index: &Index, e: &ParseGenericsError) -> Diagnostic {
match &e.cause {
mabo_parser::error::ParseGenericsCause::Parser(_, at) => {
Diagnostic::new_simple(get_range(index, *at..*at), e.to_string())
Expand All @@ -179,7 +176,7 @@ fn parse_generics_diagnostic(index: &LineIndex, e: &ParseGenericsError) -> Diagn
}
}

fn parse_fields_diagnostic(index: &LineIndex, e: &ParseFieldsError) -> Diagnostic {
fn parse_fields_diagnostic(index: &Index, e: &ParseFieldsError) -> Diagnostic {
match &e.cause {
ParseFieldsCause::Parser(_, at) => {
Diagnostic::new_simple(get_range(index, *at..*at), e.to_string())
Expand All @@ -193,11 +190,11 @@ fn parse_fields_diagnostic(index: &LineIndex, e: &ParseFieldsError) -> Diagnosti
}
}

fn parse_id_diagnostic(index: &LineIndex, e: &ParseIdError) -> Diagnostic {
fn parse_id_diagnostic(index: &Index, e: &ParseIdError) -> Diagnostic {
Diagnostic::new_simple(get_range(index, e.at.clone()), e.to_string())
}

fn validate_schema_diagnostic(file: Url, index: &LineIndex, e: validate::Error) -> Diagnostic {
fn validate_schema_diagnostic(file: Url, index: &Index, e: validate::Error) -> Diagnostic {
use validate::{DuplicateFieldId, DuplicateId, DuplicateName, Error, InvalidGenericType};

let (message, first, second) = match e {
Expand Down Expand Up @@ -244,24 +241,9 @@ fn diagnostic_with_related(
Diagnostic::new(range, None, None, None, message, Some(related), None)
}

#[allow(clippy::cast_possible_truncation, clippy::expect_used)]
fn get_range(index: &LineIndex, location: Range<usize>) -> lsp::Range {
let start = index
.to_wide(
WideEncoding::Utf16,
index.line_col(TextSize::new(location.start as u32)),
)
.expect("missing utf-16 start position");

let end = index
.to_wide(
WideEncoding::Utf16,
index.line_col(TextSize::new(location.end as u32)),
)
.expect("missing utf-16 end position");

lsp::Range::new(
lsp::Position::new(start.line, start.col),
lsp::Position::new(end.line, end.col),
)
#[allow(clippy::expect_used)]
fn get_range(index: &Index, location: Range<usize>) -> lsp::Range {
index
.get_range(location)
.expect("missing range information")
}
73 changes: 27 additions & 46 deletions crates/mabo-lsp/src/handlers/document_symbols.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
use std::{fmt::Write, ops::Range};

use anyhow::{Context, Result};
use line_index::{LineIndex, TextSize};
use lsp_types::{DocumentSymbol, Position, Range as LspRange, SymbolKind};
use anyhow::Result;
use lsp_types::{self as lsp, DocumentSymbol, SymbolKind};
use mabo_parser::{
Const, Definition, Enum, Fields, Import, Module, NamedField, Schema, Span, Spanned, Struct,
Const, Definition, Enum, Fields, Import, Module, NamedField, Schema, Spanned, Struct,
TypeAlias, UnnamedField, Variant,
};

pub fn visit_schema(index: &LineIndex, item: &Schema<'_>) -> Result<Vec<DocumentSymbol>> {
use super::index::Index;

pub fn visit_schema(index: &Index, item: &Schema<'_>) -> Result<Vec<DocumentSymbol>> {
item.definitions
.iter()
.map(|def| visit_definition(index, def))
.collect()
}

fn visit_definition(index: &LineIndex, item: &Definition<'_>) -> Result<DocumentSymbol> {
fn visit_definition(index: &Index, item: &Definition<'_>) -> Result<DocumentSymbol> {
match item {
Definition::Module(m) => visit_module(index, m),
Definition::Struct(s) => visit_struct(index, s),
Expand All @@ -26,49 +27,49 @@ fn visit_definition(index: &LineIndex, item: &Definition<'_>) -> Result<Document
}
}

fn visit_module(index: &LineIndex, item: &Module<'_>) -> Result<DocumentSymbol> {
fn visit_module(index: &Index, item: &Module<'_>) -> Result<DocumentSymbol> {
Ok(create_symbol(
item.name.get(),
SymbolKind::MODULE,
get_range(index, item.name.span())?,
index.get_range(item.name.span())?,
item.definitions
.iter()
.map(|def| visit_definition(index, def))
.collect::<Result<_>>()?,
))
}

fn visit_struct(index: &LineIndex, item: &Struct<'_>) -> Result<DocumentSymbol> {
fn visit_struct(index: &Index, item: &Struct<'_>) -> Result<DocumentSymbol> {
Ok(create_symbol(
item.name.get(),
SymbolKind::STRUCT,
get_range(index, item.name.span())?,
index.get_range(item.name.span())?,
visit_fields(index, &item.fields)?,
))
}

fn visit_enum(index: &LineIndex, item: &Enum<'_>) -> Result<DocumentSymbol> {
fn visit_enum(index: &Index, item: &Enum<'_>) -> Result<DocumentSymbol> {
Ok(create_symbol(
item.name.get(),
SymbolKind::ENUM,
get_range(index, item.name.span())?,
index.get_range(item.name.span())?,
item.variants
.iter()
.map(|variant| visit_variant(index, variant))
.collect::<Result<_>>()?,
))
}

fn visit_variant(index: &LineIndex, item: &Variant<'_>) -> Result<DocumentSymbol> {
fn visit_variant(index: &Index, item: &Variant<'_>) -> Result<DocumentSymbol> {
Ok(create_symbol(
item.name.get(),
SymbolKind::ENUM_MEMBER,
get_range(index, item.name.span())?,
index.get_range(item.name.span())?,
visit_fields(index, &item.fields)?,
))
}

fn visit_fields(index: &LineIndex, item: &Fields<'_>) -> Result<Vec<DocumentSymbol>> {
fn visit_fields(index: &Index, item: &Fields<'_>) -> Result<Vec<DocumentSymbol>> {
match item {
Fields::Named(named) => named
.iter()
Expand All @@ -83,47 +84,47 @@ fn visit_fields(index: &LineIndex, item: &Fields<'_>) -> Result<Vec<DocumentSymb
}
}

fn visit_named_field(index: &LineIndex, item: &NamedField<'_>) -> Result<DocumentSymbol> {
fn visit_named_field(index: &Index, item: &NamedField<'_>) -> Result<DocumentSymbol> {
Ok(create_symbol(
item.name.get(),
SymbolKind::PROPERTY,
get_range(index, item.name.span())?,
index.get_range(item.name.span())?,
vec![],
))
}

fn visit_unnamed_field(
index: &LineIndex,
index: &Index,
item: &UnnamedField<'_>,
pos: usize,
) -> Result<DocumentSymbol> {
Ok(create_symbol(
&pos.to_string(),
SymbolKind::PROPERTY,
get_range(index, item.span())?,
index.get_range(item.span())?,
vec![],
))
}

fn visit_alias(index: &LineIndex, item: &TypeAlias<'_>) -> Result<DocumentSymbol> {
fn visit_alias(index: &Index, item: &TypeAlias<'_>) -> Result<DocumentSymbol> {
Ok(create_symbol(
item.name.get(),
SymbolKind::VARIABLE,
get_range(index, item.name.span())?,
index.get_range(item.name.span())?,
vec![],
))
}

fn visit_const(index: &LineIndex, item: &Const<'_>) -> Result<DocumentSymbol> {
fn visit_const(index: &Index, item: &Const<'_>) -> Result<DocumentSymbol> {
Ok(create_symbol(
item.name.get(),
SymbolKind::CONSTANT,
get_range(index, item.name.span())?,
index.get_range(item.name.span())?,
vec![],
))
}

fn visit_import(index: &LineIndex, item: &Import<'_>) -> Result<DocumentSymbol> {
fn visit_import(index: &Index, item: &Import<'_>) -> Result<DocumentSymbol> {
debug_assert!(
!item.segments.is_empty(),
"there should always be at least one segment"
Expand All @@ -145,36 +146,16 @@ fn visit_import(index: &LineIndex, item: &Import<'_>) -> Result<DocumentSymbol>
Ok(create_symbol(
&name,
SymbolKind::FILE,
get_range(index, span.into())?,
index.get_range(span)?,
vec![],
))
}

#[allow(clippy::cast_possible_truncation)]
fn get_range(index: &LineIndex, span: Span) -> Result<LspRange> {
let range = Range::from(span);
let (start, end) = index
.to_wide(
line_index::WideEncoding::Utf16,
index.line_col(TextSize::new(range.start as u32)),
)
.zip(index.to_wide(
line_index::WideEncoding::Utf16,
index.line_col(TextSize::new(range.end as u32)),
))
.context("missing utf-16 positions")?;

Ok(LspRange::new(
Position::new(start.line, start.col),
Position::new(end.line, end.col),
))
}

#[allow(deprecated)]
fn create_symbol(
name: &str,
kind: SymbolKind,
range: LspRange,
range: lsp::Range,
children: Vec<DocumentSymbol>,
) -> DocumentSymbol {
DocumentSymbol {
Expand Down
50 changes: 9 additions & 41 deletions crates/mabo-lsp/src/handlers/hover.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,25 @@
use std::{fmt::Write, ops::Range};

use anyhow::{Context, Result};
use line_index::{LineIndex, TextSize, WideLineCol};
use lsp_types::{Position, Range as LspRange};
use anyhow::Result;
use lsp_types as lsp;
use mabo_compiler::simplify::{
Const, Definition, Enum, Field, Fields, Module, ParserField, Schema, Struct, TypeAlias, Variant,
};
use mabo_parser::{Span, Spanned};

use super::index::Index;

pub fn visit_schema(
index: &LineIndex,
index: &Index,
item: &Schema<'_>,
position: Position,
) -> Result<Option<(String, LspRange)>> {
let position = index
.offset(
index
.to_utf8(
line_index::WideEncoding::Utf16,
WideLineCol {
line: position.line,
col: position.character,
},
)
.context("missing utf-16 position")?,
)
.context("missing offset position")?
.into();
position: lsp::Position,
) -> Result<Option<(String, lsp::Range)>> {
let position = index.get_offset(position)?;

item.definitions
.iter()
.find_map(|def| visit_definition(def, position))
.map(|(text, span)| Ok((text, get_range(index, span)?)))
.map(|(text, span)| Ok((text, index.get_range(span)?)))
.transpose()
}

Expand Down Expand Up @@ -146,23 +134,3 @@ fn fold_comment(comment: &[&str]) -> String {
acc
})
}

#[allow(clippy::cast_possible_truncation)]
fn get_range(index: &LineIndex, span: Span) -> Result<LspRange> {
let range = Range::from(span);
let (start, end) = index
.to_wide(
line_index::WideEncoding::Utf16,
index.line_col(TextSize::new(range.start as u32)),
)
.zip(index.to_wide(
line_index::WideEncoding::Utf16,
index.line_col(TextSize::new(range.end as u32)),
))
.context("missing utf-16 positions")?;

Ok(LspRange::new(
Position::new(start.line, start.col),
Position::new(end.line, end.col),
))
}
Loading

0 comments on commit e6d4884

Please sign in to comment.