Full AST based syntax highlighting
This commit is contained in:
@@ -14,3 +14,4 @@ scc = "3.6.9"
|
||||
rust_search = "2.1.0"
|
||||
rangemap = "1.7.1"
|
||||
boxcar = "0.2.14"
|
||||
ropey = "1.6.1"
|
||||
|
||||
+12
-6
@@ -1,3 +1,4 @@
|
||||
use crate::parsing::semantic_tokens;
|
||||
use crate::utils::UriUtils;
|
||||
use crate::workspace::{Workspace, start_workspace_thread};
|
||||
use std::sync::Arc;
|
||||
@@ -6,6 +7,8 @@ use tower_lsp_server::jsonrpc::Result;
|
||||
use tower_lsp_server::ls_types::request::{GotoDeclarationParams, GotoDeclarationResponse};
|
||||
use tower_lsp_server::{Client, LspService, Server};
|
||||
use tower_lsp_server::{LanguageServer, ls_types::*};
|
||||
|
||||
mod parsing;
|
||||
mod utils;
|
||||
mod workspace;
|
||||
|
||||
@@ -36,6 +39,7 @@ impl LanguageServer for Backend {
|
||||
save: Some(TextDocumentSyncSaveOptions::SaveOptions(SaveOptions {
|
||||
include_text: Some(false),
|
||||
})),
|
||||
change: Some(TextDocumentSyncKind::INCREMENTAL),
|
||||
..Default::default()
|
||||
},
|
||||
)),
|
||||
@@ -43,15 +47,11 @@ impl LanguageServer for Backend {
|
||||
SemanticTokensServerCapabilities::SemanticTokensOptions(
|
||||
SemanticTokensOptions {
|
||||
legend: SemanticTokensLegend {
|
||||
token_types: vec![
|
||||
SemanticTokenType::TYPE,
|
||||
SemanticTokenType::FUNCTION,
|
||||
SemanticTokenType::NUMBER,
|
||||
],
|
||||
token_types: semantic_tokens::TOKENS.to_vec(),
|
||||
token_modifiers: vec![],
|
||||
},
|
||||
full: Some(SemanticTokensFullOptions::Bool(true)),
|
||||
range: None,
|
||||
range: Some(false),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
@@ -104,6 +104,12 @@ impl LanguageServer for Backend {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
async fn did_change(&self, params: DidChangeTextDocumentParams) {
|
||||
if let Some(w) = self.find_workspace(¶ms.text_document.uri).await {
|
||||
w.file_changed(params).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn did_save(&self, params: DidSaveTextDocumentParams) {
|
||||
if let Some(w) = self.find_workspace(¶ms.text_document.uri).await {
|
||||
w.reload().await;
|
||||
|
||||
@@ -0,0 +1,334 @@
|
||||
use crate::utils::{correct_semantic_token_deltas, make_diagnostics};
|
||||
use leaf_compiler::metadata::CodePosition;
|
||||
use leaf_parser::{SourceCode, Text, ast::*};
|
||||
use std::{sync::Arc, time::Instant};
|
||||
use tower_lsp_server::ls_types::{Diagnostic, SemanticToken, SemanticTokens, SemanticTokensResult};
|
||||
|
||||
pub mod semantic_tokens {
|
||||
use tower_lsp_server::ls_types::SemanticTokenType;
|
||||
|
||||
pub const TOKENS: &[SemanticTokenType] = &[
|
||||
SemanticTokenType::KEYWORD,
|
||||
SemanticTokenType::VARIABLE,
|
||||
SemanticTokenType::TYPE,
|
||||
SemanticTokenType::FUNCTION,
|
||||
SemanticTokenType::NUMBER,
|
||||
SemanticTokenType::PARAMETER,
|
||||
SemanticTokenType::PROPERTY,
|
||||
SemanticTokenType::OPERATOR,
|
||||
];
|
||||
pub const KEYWORD: u32 = 0;
|
||||
pub const VARIABLE: u32 = 1;
|
||||
pub const TYPE: u32 = 2;
|
||||
pub const FUNCTION: u32 = 3;
|
||||
pub const NUMBER: u32 = 4;
|
||||
pub const PARAMETER: u32 = 5;
|
||||
pub const PROPERTY: u32 = 6;
|
||||
pub const OPERATOR: u32 = 7;
|
||||
}
|
||||
|
||||
pub struct DocumentParsingResult {
|
||||
pub tokens: SemanticTokensResult,
|
||||
pub diagnostics: Vec<Diagnostic>,
|
||||
}
|
||||
|
||||
pub fn parse_document(code: &Arc<SourceCode>) -> DocumentParsingResult {
|
||||
let now = Instant::now();
|
||||
let ast = match code.ast() {
|
||||
Ok(d) => d,
|
||||
Err(err) => {
|
||||
return DocumentParsingResult {
|
||||
tokens: SemanticTokensResult::Tokens(SemanticTokens::default()),
|
||||
diagnostics: make_diagnostics(
|
||||
&leaf_compiler::diagnostics::Diagnostic::parsing_err(code.clone(), err.clone()),
|
||||
),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
let mut tokens = SemanticTokens::default();
|
||||
for decl in &ast.decls {
|
||||
decl.push_semantic_tokens(code, &mut tokens, None);
|
||||
}
|
||||
correct_semantic_token_deltas(&mut tokens);
|
||||
|
||||
let size = match &code.text {
|
||||
// #[cfg(feature = "rope")]
|
||||
// Text::Rope(rope) => rope.byte_len(),
|
||||
Text::ArcStr(arc_str, _) => arc_str.len(),
|
||||
};
|
||||
|
||||
eprintln!(
|
||||
"Parsed {:?} ({}KB) in {:?}",
|
||||
code.file,
|
||||
size as f32 / 1000.0,
|
||||
now.elapsed()
|
||||
);
|
||||
DocumentParsingResult {
|
||||
tokens: SemanticTokensResult::Tokens(tokens),
|
||||
diagnostics: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! push_token {
|
||||
($file: expr, $tokens: expr, $range: expr, $type: expr) => {{
|
||||
let position = CodePosition::new($file.clone(), $range.clone());
|
||||
let line_col = position.line_col();
|
||||
$tokens.data.push(SemanticToken {
|
||||
delta_line: line_col.start.line as u32,
|
||||
delta_start: line_col.start.column as u32,
|
||||
length: position.range.len() as u32,
|
||||
token_type: $type,
|
||||
token_modifiers_bitset: 0,
|
||||
});
|
||||
}};
|
||||
}
|
||||
|
||||
trait PushSemanticTokens {
|
||||
fn push_semantic_tokens(
|
||||
&self,
|
||||
file: &Arc<SourceCode>,
|
||||
tokens: &mut SemanticTokens,
|
||||
hint: Option<u32>,
|
||||
);
|
||||
}
|
||||
|
||||
impl PushSemanticTokens for AstNode<ConstDecl> {
|
||||
fn push_semantic_tokens(
|
||||
&self,
|
||||
file: &Arc<SourceCode>,
|
||||
tokens: &mut SemanticTokens,
|
||||
hint: Option<u32>,
|
||||
) {
|
||||
match self.names.as_slice() {
|
||||
[ident] => {
|
||||
push_token!(
|
||||
file,
|
||||
tokens,
|
||||
ident,
|
||||
match &self.value.node {
|
||||
Expr::Func(_) => semantic_tokens::FUNCTION,
|
||||
Expr::Ptr { .. } => semantic_tokens::TYPE,
|
||||
Expr::Struct { .. } => semantic_tokens::TYPE,
|
||||
_ => semantic_tokens::VARIABLE,
|
||||
}
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.value.push_semantic_tokens(file, tokens, hint);
|
||||
}
|
||||
}
|
||||
|
||||
impl PushSemanticTokens for AstNode<Expr> {
|
||||
fn push_semantic_tokens(
|
||||
&self,
|
||||
file: &Arc<SourceCode>,
|
||||
tokens: &mut SemanticTokens,
|
||||
hint: Option<u32>,
|
||||
) {
|
||||
match &self.node {
|
||||
Expr::Ident(range) => {
|
||||
push_token!(
|
||||
file,
|
||||
tokens,
|
||||
range,
|
||||
hint.unwrap_or(semantic_tokens::VARIABLE)
|
||||
);
|
||||
}
|
||||
Expr::Number(_) => {
|
||||
push_token!(file, tokens, self.range, semantic_tokens::NUMBER);
|
||||
}
|
||||
Expr::If(expr) => expr.push_semantic_tokens(file, tokens, None),
|
||||
Expr::While(expr) => expr.push_semantic_tokens(file, tokens, None),
|
||||
Expr::Func(func) => {
|
||||
let Function {
|
||||
fn_tok,
|
||||
args,
|
||||
ret,
|
||||
block,
|
||||
..
|
||||
} = &func.node;
|
||||
push_token!(file, tokens, fn_tok, semantic_tokens::KEYWORD);
|
||||
for AstNode {
|
||||
node: Parameter { name, value },
|
||||
..
|
||||
} in args
|
||||
{
|
||||
push_token!(file, tokens, name, semantic_tokens::PARAMETER);
|
||||
value.push_semantic_tokens(file, tokens, Some(semantic_tokens::TYPE));
|
||||
}
|
||||
if let Some(ret) = ret {
|
||||
push_token!(file, tokens, ret.range, semantic_tokens::TYPE);
|
||||
}
|
||||
if let Some(block) = block {
|
||||
block.push_semantic_tokens(file, tokens, hint);
|
||||
}
|
||||
}
|
||||
Expr::VarDecl(expr) => {
|
||||
let VarDecl {
|
||||
names,
|
||||
r#type,
|
||||
value,
|
||||
} = &**expr;
|
||||
for range in names.as_slice() {
|
||||
push_token!(file, tokens, range, semantic_tokens::VARIABLE);
|
||||
}
|
||||
if let Some(ty) = r#type {
|
||||
ty.push_semantic_tokens(file, tokens, Some(semantic_tokens::TYPE));
|
||||
}
|
||||
value.push_semantic_tokens(file, tokens, hint);
|
||||
}
|
||||
Expr::ConstDecl(expr) => {
|
||||
let ConstDecl {
|
||||
names,
|
||||
r#type,
|
||||
value,
|
||||
} = &**expr;
|
||||
for range in names.as_slice() {
|
||||
push_token!(file, tokens, range, semantic_tokens::VARIABLE);
|
||||
}
|
||||
if let Some(ty) = r#type {
|
||||
ty.push_semantic_tokens(file, tokens, Some(semantic_tokens::TYPE));
|
||||
}
|
||||
value.push_semantic_tokens(file, tokens, hint);
|
||||
}
|
||||
Expr::Ptr { mutable, base, .. } => {
|
||||
if let Some(mutable) = mutable {
|
||||
push_token!(file, tokens, mutable, semantic_tokens::KEYWORD);
|
||||
}
|
||||
base.push_semantic_tokens(file, tokens, hint);
|
||||
}
|
||||
Expr::Struct { fields, struct_tok } => {
|
||||
push_token!(file, tokens, struct_tok, semantic_tokens::KEYWORD);
|
||||
for AstNode {
|
||||
node: Field {
|
||||
name,
|
||||
public,
|
||||
mutable,
|
||||
ty,
|
||||
},
|
||||
..
|
||||
} in fields
|
||||
{
|
||||
if let Some(public) = public {
|
||||
push_token!(file, tokens, public, semantic_tokens::KEYWORD);
|
||||
}
|
||||
if let Some(mutable) = mutable {
|
||||
push_token!(file, tokens, mutable, semantic_tokens::KEYWORD);
|
||||
}
|
||||
push_token!(file, tokens, name, semantic_tokens::PROPERTY);
|
||||
ty.push_semantic_tokens(file, tokens, Some(semantic_tokens::TYPE));
|
||||
}
|
||||
}
|
||||
Expr::StructCtor { ty, values } => {
|
||||
if let Some(ty) = ty {
|
||||
push_token!(file, tokens, ty.range, semantic_tokens::TYPE);
|
||||
}
|
||||
for value in values {
|
||||
value.push_semantic_tokens(file, tokens, None);
|
||||
}
|
||||
}
|
||||
Expr::Deref { value, operator } => {
|
||||
value.push_semantic_tokens(file, tokens, None);
|
||||
push_token!(file, tokens, operator, semantic_tokens::OPERATOR);
|
||||
}
|
||||
Expr::Binary {
|
||||
lhs,
|
||||
operator: AstNode {
|
||||
node: BinaryOperator::Cast,
|
||||
range,
|
||||
},
|
||||
rhs,
|
||||
} => {
|
||||
lhs.push_semantic_tokens(file, tokens, None);
|
||||
push_token!(file, tokens, range, semantic_tokens::KEYWORD);
|
||||
rhs.push_semantic_tokens(file, tokens, Some(semantic_tokens::TYPE));
|
||||
}
|
||||
Expr::Binary {
|
||||
lhs,
|
||||
rhs,
|
||||
operator: AstNode { range, .. },
|
||||
} => {
|
||||
lhs.push_semantic_tokens(file, tokens, None);
|
||||
rhs.push_semantic_tokens(file, tokens, None);
|
||||
push_token!(file, tokens, range, semantic_tokens::OPERATOR);
|
||||
}
|
||||
Expr::Access { value, field, .. } => {
|
||||
push_token!(file, tokens, value.range, semantic_tokens::VARIABLE);
|
||||
push_token!(file, tokens, field, semantic_tokens::PROPERTY);
|
||||
value.push_semantic_tokens(file, tokens, None);
|
||||
}
|
||||
Expr::Call { func, args } => {
|
||||
push_token!(file, tokens, func.range, semantic_tokens::FUNCTION);
|
||||
for arg in args.iter() {
|
||||
arg.push_semantic_tokens(file, tokens, None);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PushSemanticTokens for Block {
|
||||
fn push_semantic_tokens(
|
||||
&self,
|
||||
file: &Arc<SourceCode>,
|
||||
tokens: &mut SemanticTokens,
|
||||
_: Option<u32>,
|
||||
) {
|
||||
for expr in &self.0 {
|
||||
expr.push_semantic_tokens(file, tokens, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PushSemanticTokens for If {
|
||||
fn push_semantic_tokens(
|
||||
&self,
|
||||
file: &Arc<SourceCode>,
|
||||
tokens: &mut SemanticTokens,
|
||||
_: Option<u32>,
|
||||
) {
|
||||
push_token!(file, tokens, self.if_tok, semantic_tokens::KEYWORD);
|
||||
self.cond.push_semantic_tokens(file, tokens, None);
|
||||
self.block.push_semantic_tokens(file, tokens, None);
|
||||
if let Some(expr) = &self.else_ {
|
||||
match &**expr {
|
||||
Else::If { else_tok, expr } => {
|
||||
push_token!(file, tokens, else_tok, semantic_tokens::KEYWORD);
|
||||
expr.push_semantic_tokens(file, tokens, None);
|
||||
}
|
||||
Else::Block { else_tok, expr } => {
|
||||
push_token!(file, tokens, else_tok, semantic_tokens::KEYWORD);
|
||||
expr.push_semantic_tokens(file, tokens, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PushSemanticTokens for While {
|
||||
fn push_semantic_tokens(
|
||||
&self,
|
||||
file: &Arc<SourceCode>,
|
||||
tokens: &mut SemanticTokens,
|
||||
_: Option<u32>,
|
||||
) {
|
||||
push_token!(file, tokens, self.while_tok, semantic_tokens::KEYWORD);
|
||||
self.cond.push_semantic_tokens(file, tokens, None);
|
||||
self.block.push_semantic_tokens(file, tokens, None);
|
||||
}
|
||||
}
|
||||
|
||||
impl PushSemanticTokens for NameValuePair {
|
||||
fn push_semantic_tokens(
|
||||
&self,
|
||||
file: &Arc<SourceCode>,
|
||||
tokens: &mut SemanticTokens,
|
||||
_: Option<u32>,
|
||||
) {
|
||||
push_token!(file, tokens, self.name, semantic_tokens::PROPERTY);
|
||||
self.value.push_semantic_tokens(file, tokens, None);
|
||||
}
|
||||
}
|
||||
+44
-1
@@ -1,5 +1,8 @@
|
||||
use leaf_compiler::metadata::CodePosition;
|
||||
use tower_lsp_server::ls_types::{Position, Range, Uri};
|
||||
use tower_lsp_server::ls_types::{
|
||||
Diagnostic, DiagnosticSeverity, NumberOrString, Position, Range, SemanticToken, SemanticTokens,
|
||||
Uri,
|
||||
};
|
||||
|
||||
pub trait UriUtils {
|
||||
fn strip_header(&self) -> &str;
|
||||
@@ -33,3 +36,43 @@ impl CodePositionUtils for CodePosition {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_diagnostics(diag: &leaf_compiler::diagnostics::Diagnostic) -> Vec<Diagnostic> {
|
||||
vec![Diagnostic {
|
||||
range: diag.position.lsp_range(),
|
||||
severity: Some(match diag.kind {
|
||||
leaf_compiler::diagnostics::Kind::Info => DiagnosticSeverity::INFORMATION,
|
||||
leaf_compiler::diagnostics::Kind::Warning => DiagnosticSeverity::WARNING,
|
||||
leaf_compiler::diagnostics::Kind::Error => DiagnosticSeverity::ERROR,
|
||||
}),
|
||||
code: Some(NumberOrString::String(format!("{:#06X}", diag.code as u32))),
|
||||
code_description: None,
|
||||
source: Some("Leaf compiler".into()),
|
||||
message: diag.message.clone(),
|
||||
related_information: None,
|
||||
tags: None,
|
||||
data: None,
|
||||
}]
|
||||
}
|
||||
|
||||
pub fn correct_semantic_token_deltas(tokens: &mut SemanticTokens) {
|
||||
let mut previous_line = 0;
|
||||
let mut previous_start = 0;
|
||||
tokens.data.sort_by_key(|v| (v.delta_line, v.delta_start));
|
||||
for SemanticToken {
|
||||
delta_line,
|
||||
delta_start,
|
||||
..
|
||||
} in tokens.data.iter_mut()
|
||||
{
|
||||
let line = *delta_line;
|
||||
let start = *delta_start;
|
||||
*delta_start = match line == previous_line {
|
||||
false => start,
|
||||
true => start - previous_start,
|
||||
};
|
||||
*delta_line = line - previous_line;
|
||||
previous_line = line;
|
||||
previous_start = start;
|
||||
}
|
||||
}
|
||||
|
||||
+121
-279
@@ -5,8 +5,9 @@ use leaf_assembly::{
|
||||
values::{AnyConst, AnyValue, Value},
|
||||
};
|
||||
use leaf_compiler::{CompilationContext, events::Event, metadata::CodePosition};
|
||||
use leaf_parser::{ArcStr, SourceCode};
|
||||
use leaf_parser::{ArcStr, SourceCode, Text};
|
||||
use rangemap::RangeMap;
|
||||
use ropey::Rope;
|
||||
use rust_search::SearchBuilder;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
@@ -14,6 +15,7 @@ use std::{
|
||||
fmt::Write,
|
||||
ops::Range,
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
use tokio::sync::{
|
||||
@@ -23,15 +25,17 @@ use tokio::sync::{
|
||||
use tower_lsp_server::{
|
||||
Client,
|
||||
ls_types::{
|
||||
Diagnostic, DiagnosticSeverity, Hover, HoverContents, HoverParams, Location, MarkedString,
|
||||
NumberOrString, Position, Range as LspRange, SemanticToken, SemanticTokenType,
|
||||
SemanticTokens, SemanticTokensParams, SemanticTokensResult, TextDocumentPositionParams,
|
||||
Uri,
|
||||
Diagnostic, DidChangeTextDocumentParams, Hover, HoverContents, HoverParams, Location,
|
||||
MarkedString, Position, SemanticToken, SemanticTokens, SemanticTokensParams,
|
||||
SemanticTokensResult, TextDocumentPositionParams, Uri,
|
||||
request::{GotoDeclarationParams, GotoDeclarationResponse},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::utils::{CodePositionUtils, UriUtils};
|
||||
use crate::{
|
||||
parsing::{DocumentParsingResult, parse_document},
|
||||
utils::{CodePositionUtils, UriUtils, correct_semantic_token_deltas, make_diagnostics},
|
||||
};
|
||||
|
||||
pub struct Workspace {
|
||||
pub base: String,
|
||||
@@ -85,6 +89,13 @@ impl Workspace {
|
||||
result.0.get().cloned()
|
||||
}
|
||||
|
||||
pub async fn file_changed(&self, params: DidChangeTextDocumentParams) {
|
||||
self.sender
|
||||
.send(Request::FileChanged(params))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub async fn reload(&self) {
|
||||
self.sender.send(Request::Reload).await.unwrap();
|
||||
}
|
||||
@@ -93,6 +104,7 @@ impl Workspace {
|
||||
enum Request {
|
||||
Reload,
|
||||
Close(Arc<Notify>),
|
||||
FileChanged(DidChangeTextDocumentParams),
|
||||
Hover(HoverParams, Arc<(OnceLock<Hover>, Notify)>),
|
||||
GoToDeclaration(
|
||||
GotoDeclarationParams,
|
||||
@@ -104,251 +116,125 @@ enum Request {
|
||||
),
|
||||
}
|
||||
|
||||
struct State<'l> {
|
||||
context: CompilationContext<'l>,
|
||||
files: Arc<RwLock<HashMap<String, FileInfo>>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct FileInfo {
|
||||
line_ranges: HashMap<u32, Range<usize>>,
|
||||
symbol_ranges: RangeMap<usize, [u8; size_of::<AnyValue<'_>>()]>,
|
||||
symbol_positions: HashMap<[u8; size_of::<AnyValue<'_>>()], CodePosition>,
|
||||
symbol_definitions: HashMap<[u8; size_of::<AnyValue<'_>>()], CodePosition>,
|
||||
}
|
||||
|
||||
impl<'l> State<'l> {
|
||||
pub fn new(alloc: &'l SyncArenaAllocator) -> Self {
|
||||
State {
|
||||
context: CompilationContext::new(alloc),
|
||||
files: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_workspace_thread(url: Uri, client: Client) -> Result<Arc<Workspace>, String> {
|
||||
let base = url.strip_header().to_string();
|
||||
let path = base.clone();
|
||||
let (sender, mut receiver) = channel(1);
|
||||
|
||||
let _handle = tokio::task::spawn(async move {
|
||||
let mut alloc = SyncArenaAllocator::default();
|
||||
let mut file_text = HashMap::<Uri, (Rope, i32)>::new();
|
||||
let mut files = HashMap::<Uri, (Arc<SourceCode>, i32)>::new();
|
||||
let mut parsed = HashMap::<Uri, DocumentParsingResult>::new();
|
||||
|
||||
macro_rules! get_file_text {
|
||||
($uri:expr, $version:expr) => {
|
||||
file_text.entry($uri).or_insert_with_key(|key| {
|
||||
let path = key.strip_header();
|
||||
(
|
||||
Rope::from_str(&std::fs::read_to_string(path).unwrap()),
|
||||
$version,
|
||||
)
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
'global: loop {
|
||||
alloc.reset();
|
||||
let mut state = State::new(&alloc);
|
||||
let mut diagnostics = HashMap::<PathBuf, Vec<Diagnostic>>::new();
|
||||
|
||||
let files: Vec<_> = tokio::task::block_in_place(|| {
|
||||
SearchBuilder::default()
|
||||
.location(&path)
|
||||
.ext("leaf")
|
||||
.build()
|
||||
.map(|f| {
|
||||
let mut info = state.files.blocking_write();
|
||||
let info = info.entry(f.clone()).or_default();
|
||||
let text: ArcStr = std::fs::read_to_string(&f).unwrap().into();
|
||||
info.line_ranges = calc_line_ranges(text.as_str());
|
||||
diagnostics.entry(PathBuf::from(&f)).or_default();
|
||||
Arc::new(SourceCode {
|
||||
text,
|
||||
file: f.into(),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
|
||||
let diagnostics = Arc::new(Mutex::new(diagnostics));
|
||||
|
||||
{
|
||||
let info = state.files.clone();
|
||||
let diagnostics = diagnostics.clone();
|
||||
state.context.add_event_callback(move |e| unsafe {
|
||||
tokio::task::block_in_place(|| match e {
|
||||
Event::Symbol { value, position } => {
|
||||
let mut info = info.blocking_write();
|
||||
let info = info
|
||||
.entry(position.file.file.to_string_lossy().to_string())
|
||||
.or_default();
|
||||
info.symbol_ranges
|
||||
.insert(position.range.clone(), std::mem::transmute(*value));
|
||||
info.symbol_positions
|
||||
.insert(std::mem::transmute(*value), position.clone());
|
||||
}
|
||||
Event::Definition { value, position } => {
|
||||
let mut info = info.blocking_write();
|
||||
let info = info
|
||||
.entry(position.file.file.to_string_lossy().to_string())
|
||||
.or_default();
|
||||
info.symbol_definitions
|
||||
.insert(std::mem::transmute(*value), position.clone());
|
||||
info.symbol_ranges
|
||||
.insert(position.range.clone(), std::mem::transmute(*value));
|
||||
info.symbol_positions
|
||||
.insert(std::mem::transmute(*value), position.clone());
|
||||
}
|
||||
Event::Diagnostic(diagnostic) => {
|
||||
let mut diagnostics = diagnostics.blocking_lock();
|
||||
diagnostics
|
||||
.entry(diagnostic.position.file.file.clone())
|
||||
.or_default()
|
||||
.extend(make_diagnostics(diagnostic));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if let Err(err) = state.context.extend(
|
||||
AssemblyIdentifier {
|
||||
version: Version::default(),
|
||||
name: Cow::Borrowed("Leaf lsp tmp"),
|
||||
},
|
||||
&files,
|
||||
) {
|
||||
let mut diagnostics = diagnostics.lock().await;
|
||||
diagnostics
|
||||
.entry(err.position.file.file.clone())
|
||||
.or_default()
|
||||
.extend(make_diagnostics(&err));
|
||||
}
|
||||
|
||||
{
|
||||
let mut diagnostics = diagnostics.lock().await;
|
||||
for (file, diagnostics) in diagnostics.drain() {
|
||||
client
|
||||
.publish_diagnostics(Uri::from_file_path(file).unwrap(), diagnostics, None)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(event) = receiver.recv().await {
|
||||
match event {
|
||||
Request::Reload => {
|
||||
alloc = SyncArenaAllocator::default();
|
||||
continue 'global;
|
||||
}
|
||||
Request::Reload => continue 'global,
|
||||
Request::Hover(params, result) => {
|
||||
let value = state
|
||||
.with_file_and_range(
|
||||
¶ms.text_document_position_params,
|
||||
|info, range| unsafe {
|
||||
let Some(symbol) = info.symbol_ranges.get(
|
||||
&(range.start
|
||||
+ params
|
||||
.text_document_position_params
|
||||
.position
|
||||
.character as usize),
|
||||
) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
// This is blasphemy but what can I do? :3
|
||||
Some(std::mem::transmute::<_, AnyValue>(*symbol))
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Some(Some(value)) = value {
|
||||
let mut message = String::new();
|
||||
let _ = writeln!(
|
||||
message,
|
||||
"Type: {}",
|
||||
match value.is_lvalue() {
|
||||
false => value.ty(),
|
||||
true => match value.ty() {
|
||||
Type::Ptr(PtrT { base, .. }) => *base,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
}
|
||||
);
|
||||
result
|
||||
.0
|
||||
.set(Hover {
|
||||
contents: HoverContents::Scalar(MarkedString::String(message)),
|
||||
range: None,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
result.1.notify_one();
|
||||
}
|
||||
Request::GoToDeclaration(params, result) => {
|
||||
let declaration = state
|
||||
.with_file_and_range(
|
||||
¶ms.text_document_position_params,
|
||||
|info, range| {
|
||||
let Some(symbol) = info.symbol_ranges.get(
|
||||
&(range.start
|
||||
+ params
|
||||
.text_document_position_params
|
||||
.position
|
||||
.character as usize),
|
||||
) else {
|
||||
return None;
|
||||
};
|
||||
let Some(position) = info.symbol_definitions.get(symbol) else {
|
||||
return None;
|
||||
};
|
||||
Some(position.clone())
|
||||
},
|
||||
result.1.notify_one();
|
||||
}
|
||||
Request::FileChanged(params) => {
|
||||
let (file, version) = get_file_text!(
|
||||
params.text_document.uri.clone(),
|
||||
params.text_document.version
|
||||
);
|
||||
eprintln!("File {:?} changed", params.text_document.uri.as_str());
|
||||
for change in params.content_changes {
|
||||
let Some(range) = change.range else {
|
||||
*file = Rope::from_str(&change.text);
|
||||
continue;
|
||||
};
|
||||
|
||||
fn pos_to_char_index(rope: &ropey::Rope, pos: Position) -> usize {
|
||||
let line_start = rope.line_to_char(pos.line as usize);
|
||||
line_start + pos.character as usize
|
||||
}
|
||||
let start = pos_to_char_index(file, range.start);
|
||||
let end = pos_to_char_index(file, range.end);
|
||||
|
||||
file.remove(start..end);
|
||||
file.insert(start, &change.text);
|
||||
}
|
||||
*version = params.text_document.version;
|
||||
client.semantic_tokens_refresh().await.unwrap();
|
||||
}
|
||||
Request::SemanticTokens(params, result) => {
|
||||
eprintln!(
|
||||
"Sending semantic tokens for file {:?}",
|
||||
params.text_document.uri.as_str()
|
||||
);
|
||||
let (file, version) = get_file_text!(params.text_document.uri.clone(), 0);
|
||||
let source = match files.get_mut(¶ms.text_document.uri) {
|
||||
None => {
|
||||
&files
|
||||
.entry(params.text_document.uri.clone())
|
||||
.or_insert_with_key(|uri| {
|
||||
(
|
||||
SourceCode {
|
||||
file: uri.strip_header().into(),
|
||||
text: Text::ArcStr(
|
||||
ArcStr::from(file.to_string()),
|
||||
OnceLock::new(),
|
||||
),
|
||||
ast: OnceLock::new(),
|
||||
}
|
||||
.into(),
|
||||
*version,
|
||||
)
|
||||
})
|
||||
.0
|
||||
}
|
||||
Some((source, v)) => {
|
||||
if *v != *version {
|
||||
eprintln!(
|
||||
"Updating file {:?}",
|
||||
params.text_document.uri.as_str()
|
||||
);
|
||||
*source = SourceCode {
|
||||
file: params.text_document.uri.strip_header().into(),
|
||||
text: Text::ArcStr(
|
||||
ArcStr::from(file.to_string()),
|
||||
OnceLock::new(),
|
||||
),
|
||||
ast: OnceLock::new(),
|
||||
}
|
||||
.into();
|
||||
*v = *version;
|
||||
parsed.remove(¶ms.text_document.uri);
|
||||
}
|
||||
source
|
||||
}
|
||||
};
|
||||
|
||||
let parsed = parsed
|
||||
.entry(params.text_document.uri.clone())
|
||||
.or_insert_with(|| parse_document(source));
|
||||
|
||||
let _ = client
|
||||
.publish_diagnostics(
|
||||
params.text_document.uri,
|
||||
parsed.diagnostics.clone(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Some(Some(decl)) = declaration {
|
||||
result
|
||||
.0
|
||||
.set(GotoDeclarationResponse::Scalar(Location {
|
||||
uri: Uri::from_file_path(&decl.file.file).unwrap(),
|
||||
range: decl.lsp_range(),
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
result.1.notify_one();
|
||||
}
|
||||
Request::SemanticTokens(params, result) => {
|
||||
let info = state.files.read().await;
|
||||
let Some(file) = info.get(params.text_document.uri.strip_header()) else {
|
||||
result.1.notify_one();
|
||||
continue;
|
||||
};
|
||||
let mut tokens = SemanticTokens::default();
|
||||
for (symbol, position) in file.symbol_positions.iter() {
|
||||
let symbol: AnyValue = unsafe { std::mem::transmute(*symbol) };
|
||||
let line_col = position.line_col();
|
||||
tokens.data.push(SemanticToken {
|
||||
delta_line: line_col.start.line as u32,
|
||||
delta_start: line_col.start.column as u32,
|
||||
length: position.range.len() as u32,
|
||||
token_type: match symbol {
|
||||
AnyValue::Constant(AnyConst::Type(_)) => 0,
|
||||
AnyValue::Constant(AnyConst::Function(_)) => 1,
|
||||
AnyValue::Constant(AnyConst::Int(_) | AnyConst::Float(_)) => 2,
|
||||
_ => continue,
|
||||
},
|
||||
token_modifiers_bitset: 0,
|
||||
});
|
||||
}
|
||||
|
||||
let mut previous_line = 0;
|
||||
let mut previous_start = 0;
|
||||
tokens.data.sort_by_key(|v| (v.delta_line, v.delta_start));
|
||||
for SemanticToken {
|
||||
delta_line,
|
||||
delta_start,
|
||||
..
|
||||
} in tokens.data.iter_mut()
|
||||
{
|
||||
let line = *delta_line;
|
||||
let start = *delta_start;
|
||||
*delta_start = match line == previous_line {
|
||||
false => start,
|
||||
true => start - previous_start,
|
||||
};
|
||||
*delta_line = line - previous_line;
|
||||
previous_line = line;
|
||||
previous_start = start;
|
||||
}
|
||||
|
||||
result.0.set(SemanticTokensResult::Tokens(tokens)).unwrap();
|
||||
result.0.set(parsed.tokens.clone()).unwrap();
|
||||
result.1.notify_one();
|
||||
}
|
||||
Request::Close(notify) => {
|
||||
@@ -361,47 +247,3 @@ pub fn start_workspace_thread(url: Uri, client: Client) -> Result<Arc<Workspace>
|
||||
});
|
||||
Ok(Arc::new(Workspace { base, sender }))
|
||||
}
|
||||
|
||||
fn calc_line_ranges(text: &str) -> HashMap<u32, Range<usize>> {
|
||||
let mut map = HashMap::new();
|
||||
for line in text.split('\n') {
|
||||
let start = line.as_ptr() as usize - text.as_ptr() as usize;
|
||||
map.insert(map.len() as u32, start..start + line.len());
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
impl State<'_> {
|
||||
async fn with_file_and_range<T>(
|
||||
&self,
|
||||
params: &TextDocumentPositionParams,
|
||||
action: impl FnOnce(&FileInfo, Range<usize>) -> T,
|
||||
) -> Option<T> {
|
||||
let info = self.files.read().await;
|
||||
let Some(info) = info.get(params.text_document.uri.strip_header()) else {
|
||||
return None;
|
||||
};
|
||||
let Some(range) = info.line_ranges.get(¶ms.position.line) else {
|
||||
return None;
|
||||
};
|
||||
Some(action(info, range.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
fn make_diagnostics(diag: &leaf_compiler::diagnostics::Diagnostic) -> Vec<Diagnostic> {
|
||||
vec![Diagnostic {
|
||||
range: diag.position.lsp_range(),
|
||||
severity: Some(match diag.kind {
|
||||
leaf_compiler::diagnostics::Kind::Info => DiagnosticSeverity::INFORMATION,
|
||||
leaf_compiler::diagnostics::Kind::Warning => DiagnosticSeverity::WARNING,
|
||||
leaf_compiler::diagnostics::Kind::Error => DiagnosticSeverity::ERROR,
|
||||
}),
|
||||
code: Some(NumberOrString::Number(diag.code as i32)),
|
||||
code_description: None,
|
||||
source: Some("Leaf compiler".into()),
|
||||
message: diag.message.clone(),
|
||||
related_information: None,
|
||||
tags: None,
|
||||
data: None,
|
||||
}]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user