Skip to content

Commit

Permalink
LS: Move outline generation to its own module, improve it slightly
Browse files Browse the repository at this point in the history
  • Loading branch information
osa1 committed Jan 30, 2024
1 parent 9c5c73d commit 7e4d059
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 62 deletions.
2 changes: 1 addition & 1 deletion crates/h10/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ mod parser;
pub mod pos;
mod scc;
mod scope_map;
mod token;
pub mod token;
mod type_inference;
mod type_scheme;
mod typing;
Expand Down
4 changes: 2 additions & 2 deletions crates/h10_language_server/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ pub struct Ast {
}

pub struct AstData {
arena: DeclArena,
decls: Vec<DeclIdx>,
pub arena: DeclArena,
pub decls: Vec<DeclIdx>,
}

impl Ast {
Expand Down
64 changes: 5 additions & 59 deletions crates/h10_language_server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@

mod ast;
mod buffer;
mod outline;
mod token;

use ast::Ast;
use buffer::{Buffer, Token};
use token::TOKEN_TYPES;

use h10_lexer::{ReservedId, TokenKind};

use std::fs::File;
use std::io::Write;
use std::sync::Mutex;
Expand Down Expand Up @@ -154,63 +153,10 @@ impl LanguageServer for Backend {
params: DocumentSymbolParams,
) -> Result<Option<DocumentSymbolResponse>> {
writeln!(self.log_file.lock().unwrap(), "{:#?}", params).unwrap();

let mut symbols: Vec<SymbolInformation> = vec![];
let url: Url = self.file_uri.lock().unwrap().clone().unwrap();
for decl in self.ast.data.lock().unwrap().iter_decls() {
let (kind, name) = match decl.first_token.kind() {
TokenKind::VarId => (SymbolKind::FUNCTION, decl.first_token.text().to_owned()),

TokenKind::ReservedId(ReservedId::Class | ReservedId::Instance) => {
let next_token = match decl.first_token.next().and_then(|t| t.next()) {
None => continue,
Some(next_token) => next_token,
};
match next_token.kind() {
TokenKind::ConId => (SymbolKind::CLASS, next_token.text().to_owned()),
_ => continue,
}
}

TokenKind::ReservedId(
ReservedId::Data | ReservedId::Newtype | ReservedId::Type,
) => {
let next_token = match decl.first_token.next().and_then(|t| t.next()) {
None => continue,
Some(next_token) => next_token,
};
match next_token.kind() {
TokenKind::ConId => (SymbolKind::STRUCT, next_token.text().to_owned()),
_ => continue,
}
}

_ => continue,
};

symbols.push(SymbolInformation {
name,
kind,
tags: None,
location: Location::new(
url.clone(),
Range {
start: Position {
line: decl.span_start().line,
character: decl.span_start().char,
},
end: Position {
line: decl.span_end().line,
character: decl.span_end().char,
},
},
),
container_name: None,
deprecated: None,
});
}

Ok(Some(DocumentSymbolResponse::Flat(symbols)))
let ast = self.ast.data.lock().unwrap();
Ok(Some(outline::generate_symbol_response(
&ast.decls, &ast.arena,
)))
}

async fn semantic_tokens_full(
Expand Down
160 changes: 160 additions & 0 deletions crates/h10_language_server/src/outline.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
//! Implements outline/symbols generation from partially parsed ASTs (indentation groups).
use h10::ast;
use h10::decl_arena::{DeclArena, DeclIdx};
use h10::token::TokenRef;
use h10_lexer::{ReservedId, TokenKind};

use tower_lsp::lsp_types as lsp;

pub fn generate_symbol_response(
decls: &[DeclIdx],
arena: &DeclArena,
) -> lsp::DocumentSymbolResponse {
// NB. LSP spec says we should return `DocumentSymbol[]` (`DocumentSymbolResponse::Nested` in
// `lsp_types`) instead of `SymbolInformation[]` (`DocumentSymbolResponse::Flat` in
// `lsp_types`) when possible, as `DocumentSymbol` contains more information.
lsp::DocumentSymbolResponse::Nested(generate_document_symbols(decls, arena))
}

fn generate_document_symbols(decls: &[DeclIdx], arena: &DeclArena) -> Vec<lsp::DocumentSymbol> {
decls
.iter()
.filter_map(|decl_idx| generate_decl_symbol(arena.get(*decl_idx)))
.collect()
}

fn generate_decl_symbol(decl: &ast::TopDecl) -> Option<lsp::DocumentSymbol> {
let mut token_iter = iter_tokens(decl);

let token0 = token_iter.next()?;
let decl_range = decl_range(decl);

// `name` is used by VSCode in "go to symbol". `detail` is used in the outline view.
Some(match token0.kind() {
// TODO: This won't work as expected in operator definitions and infix names. E.g.
//
// ```
// a `add` b = ...
// a :+: b = ...
// ```
TokenKind::VarId => lsp::DocumentSymbol {
name: token0.text().to_owned(),
// TODO: Consider showing symbol type as detail.
detail: None,
kind: lsp::SymbolKind::FUNCTION,
deprecated: None,
tags: None,
range: decl_range,
selection_range: decl_range,
children: None,
},

TokenKind::ReservedId(ReservedId::Class) => {
let mut name = String::with_capacity(50);
let name_token = token0.next()?;
// Start new iteration as `token_iter` skips whitespace
for token in name_token.iter() {
match token.kind() {
TokenKind::ReservedId(ReservedId::Where) => break,
TokenKind::Whitespace => name.push(' '),
TokenKind::Comment { .. } => continue,
_ => {
let token_str = token.text();
if name.len() + token_str.len() >= 50 {
break;
}
name.push_str(token_str);
}
}
}

lsp::DocumentSymbol {
name,
detail: None,
kind: lsp::SymbolKind::INTERFACE,
deprecated: None,
tags: None,
range: decl_range,
selection_range: decl_range,
children: None,
}
}

TokenKind::ReservedId(ReservedId::Instance) => {
let mut name = String::with_capacity(50);
let name_token = token0.next()?;
for token in name_token.iter() {
match token.kind() {
TokenKind::ReservedId(ReservedId::Where) => break,
TokenKind::Whitespace => name.push(' '),
_ => {
let token_str = token.text();
if name.len() + token_str.len() >= 50 {
break;
}
name.push_str(token_str);
}
}
}

lsp::DocumentSymbol {
name,
detail: None,
kind: lsp::SymbolKind::METHOD,
deprecated: None,
tags: None,
range: decl_range,
selection_range: decl_range,
children: None,
}
}

TokenKind::ReservedId(ReservedId::Data | ReservedId::Newtype | ReservedId::Type) => {
let name_token = token_iter.next()?;
let name = match name_token.kind() {
TokenKind::VarId
| TokenKind::ConId
| TokenKind::VarSym
| TokenKind::ConSym
| TokenKind::QVarId
| TokenKind::QVarSym => name_token.text().to_owned(),
_ => return None,
};

lsp::DocumentSymbol {
name,
detail: None,
kind: lsp::SymbolKind::CLASS,
deprecated: None,
tags: None,
range: decl_range,
selection_range: decl_range,
children: None,
}
}

_ => return None,
})
}

/// Iterate tokens of a declaration, skipping whitespace and comments.
fn iter_tokens(decl: &ast::TopDecl) -> impl Iterator<Item = TokenRef> {
decl.iter_tokens()
.filter(|t| !matches!(t.kind(), TokenKind::Whitespace | TokenKind::Comment { .. }))
}

fn decl_range(decl: &ast::TopDecl) -> lsp::Range {
let start = decl.span_start();
let end = decl.span_end();
lsp::Range {
start: lsp::Position {
line: start.line,
character: start.char,
},
end: lsp::Position {
line: end.line,
character: end.char,
},
}
}

0 comments on commit 7e4d059

Please sign in to comment.