From cf51c1855245c970f757c425332fc65eafad05b5 Mon Sep 17 00:00:00 2001 From: sofia-bobbiesi Date: Mon, 22 Dec 2025 14:40:57 -0300 Subject: [PATCH 1/2] feat: improved protocol loading and symbol resolution --- src/server.rs | 216 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 153 insertions(+), 63 deletions(-) diff --git a/src/server.rs b/src/server.rs index ca91b92..39d5f07 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,6 +1,6 @@ use serde_json::Value; use tower_lsp::{jsonrpc::Result, lsp_types::*, LanguageServer}; -use tx3_lang::ast::Identifier; +use tx3_lang::ast::{Identifier, Symbol}; use crate::{ cmds, position_to_offset, span_contains, span_to_lsp_range, @@ -413,78 +413,168 @@ impl LanguageServer for Context { let mut symbols: Vec = Vec::new(); let uri = ¶ms.text_document.uri; - let document = self.documents.get(uri); - if let Some(document) = document { - let text = document.value().to_string(); - let ast = tx3_lang::parsing::parse_string(text.as_str()); - if ast.is_ok() { - let ast = ast.unwrap(); - for party in ast.parties { - symbols.push(make_symbol( - party.name.value.clone(), - "Party".to_string(), - SymbolKind::OBJECT, - span_to_lsp_range(document.value(), &party.span), - None, - )); - } - for policy in ast.policies { - symbols.push(make_symbol( - policy.name.value.clone(), - "Policy".to_string(), - SymbolKind::KEY, - span_to_lsp_range(document.value(), &policy.span), - None, - )); - } - - for tx in ast.txs { - let mut children: Vec = Vec::new(); - for parameter in tx.parameters.parameters { - children.push(make_symbol( - parameter.name.value.clone(), - format!("Parameter<{:?}>", parameter.r#type), - SymbolKind::FIELD, - span_to_lsp_range(document.value(), &tx.parameters.span), - None, - )); - } + let Ok(path) = uri.to_file_path() else { + return Ok(None); + }; - for input in tx.inputs { - children.push(make_symbol( - input.name.clone(), - "Input".to_string(), - SymbolKind::OBJECT, - span_to_lsp_range(document.value(), &input.span), - None, - )); - } + let loader = tx3_lang::loading::ProtocolLoader::from_file(&path); + let Ok(protocol) = loader.load() else { + return Ok(None); + }; - for (i, output) in tx.outputs.iter().enumerate() { - let default_output = Identifier::new(format!("output {}", i + 1)); + let Some(document) = self.documents.get(uri) else { + return Ok(None); + }; - let name = output.name.as_ref().unwrap_or(&default_output); + let ast = protocol.ast(); - children.push(make_symbol( - name.value.clone(), - "Output".to_string(), + if let Some(scope) = ast.scope() { + let mut current_scope = Some(scope); + while let Some(s) = current_scope { + for (name, symbol) in s.symbols() { + let (kind, range, detail) = match symbol { + Symbol::PartyDef(def) => ( SymbolKind::OBJECT, - span_to_lsp_range(document.value(), &output.span), - None, - )); - } + span_to_lsp_range(document.value(), &def.span), + "Party".to_string(), + ), + Symbol::PolicyDef(def) => ( + SymbolKind::KEY, + span_to_lsp_range(document.value(), &def.span), + "Policy".to_string(), + ), + Symbol::AssetDef(def) => ( + SymbolKind::FIELD, + span_to_lsp_range(document.value(), &def.span), + "Asset".to_string(), + ), + Symbol::TypeDef(def) => ( + SymbolKind::TYPE_PARAMETER, + span_to_lsp_range(document.value(), &def.span), + "Type".to_string(), + ), + Symbol::AliasDef(def) => ( + SymbolKind::INTERFACE, + span_to_lsp_range(document.value(), &def.span), + "Alias".to_string(), + ), + Symbol::RecordField(def) => ( + SymbolKind::FIELD, + span_to_lsp_range(document.value(), &def.span), + "Field".to_string(), + ), + Symbol::VariantCase(def) => ( + SymbolKind::ENUM_MEMBER, + span_to_lsp_range(document.value(), &def.span), + "Variant Case".to_string(), + ), + Symbol::EnvVar(_, ty) => ( + SymbolKind::VARIABLE, + Range::default(), + format!("EnvVar({:?})", ty), + ), + Symbol::ParamVar(_, ty) => ( + SymbolKind::VARIABLE, + Range::default(), + format!("ParamVar({:?})", ty), + ), + Symbol::LocalExpr(_) => ( + SymbolKind::VARIABLE, + Range::default(), + "LocalExpr".to_string(), + ), + Symbol::Output(idx) => ( + SymbolKind::OBJECT, + Range::default(), + format!("Output({})", idx), + ), + Symbol::Input(_) => { + (SymbolKind::OBJECT, Range::default(), "Input".to_string()) + } + Symbol::Fees => { + (SymbolKind::CONSTANT, Range::default(), "Fees".to_string()) + } + }; - symbols.push(make_symbol( - tx.name.value.clone(), - "Tx".to_string(), - SymbolKind::METHOD, - span_to_lsp_range(document.value(), &tx.span), - Some(children), - )); + symbols.push(make_symbol(name.clone(), detail, kind, range, None)); } + current_scope = s.parent(); + } + } + + for tx in &ast.txs { + let mut children: Vec = Vec::new(); + for parameter in &tx.parameters.parameters { + children.push(make_symbol( + parameter.name.value.clone(), + format!("Parameter<{:?}>", parameter.r#type), + SymbolKind::FIELD, + span_to_lsp_range(document.value(), &tx.parameters.span), + None, + )); + } + + for input in &tx.inputs { + children.push(make_symbol( + input.name.clone(), + "Input".to_string(), + SymbolKind::OBJECT, + span_to_lsp_range(document.value(), &input.span), + None, + )); } + + for (i, output) in tx.outputs.iter().enumerate() { + let default_output = Identifier::new(format!("output {}", i + 1)); + + let name = output.name.as_ref().unwrap_or(&default_output); + + children.push(make_symbol( + name.value.clone(), + "Output".to_string(), + SymbolKind::OBJECT, + span_to_lsp_range(document.value(), &output.span), + None, + )); + } + + symbols.push(make_symbol( + tx.name.value.clone(), + "Tx".to_string(), + SymbolKind::METHOD, + span_to_lsp_range(document.value(), &tx.span), + Some(children), + )); + } + + let mut groups: std::collections::HashMap> = + std::collections::HashMap::new(); + + for symbol in symbols { + let key = symbol.detail.clone().unwrap(); + groups.entry(key).or_default().push(symbol); + } + + let mut symbols: Vec = Vec::new(); + let mut keys: Vec = groups.keys().cloned().collect(); + keys.sort(); + + for key in keys { + let mut children = groups.remove(&key).unwrap(); + children.sort_by(|a, b| a.name.cmp(&b.name)); + + let range = children.first().map(|c| c.range).unwrap_or_default(); + + symbols.push(make_symbol( + key, + String::new(), + SymbolKind::ENUM, + range, + Some(children), + )); } + Ok(Some(DocumentSymbolResponse::Nested(symbols))) } From 4ce215a886f8678bf4317c36084da7a1f81c1924 Mon Sep 17 00:00:00 2001 From: sofia-bobbiesi Date: Wed, 25 Feb 2026 13:03:19 -0300 Subject: [PATCH 2/2] feat: support importing types in document processing --- src/lib.rs | 10 ++- src/server.rs | 193 ++++++++++++++++++++++++++++---------------------- 2 files changed, 117 insertions(+), 86 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 08297de..75be803 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -316,7 +316,8 @@ impl Context { fn get_document_program(&self, url_arg: &str) -> Result { let document = self.get_document(url_arg)?; - tx3_lang::parsing::parse_string(document.to_string().as_str()).map_err(Error::ProgramParsingError) + tx3_lang::parsing::parse_string(document.to_string().as_str()) + .map_err(Error::ProgramParsingError) } async fn process_document(&self, uri: Url, text: &str) -> Vec { @@ -327,6 +328,13 @@ impl Context { match ast { Ok(mut ast) => { + if let Ok(path) = uri.to_file_path() { + if let Some(root) = path.parent() { + let loader = tx3_lang::importing::FsLoader::new(root); + let _ = tx3_lang::importing::resolve_imports(&mut ast, Some(&loader)); + } + } + let analysis = tx3_lang::analyzing::analyze(&mut ast); analyze_report_to_diagnostic(&rope, &analysis) } diff --git a/src/server.rs b/src/server.rs index 39d5f07..4ad9c12 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,6 +1,7 @@ +use ropey::Rope; use serde_json::Value; use tower_lsp::{jsonrpc::Result, lsp_types::*, LanguageServer}; -use tx3_lang::ast::{Identifier, Symbol}; +use tx3_lang::ast::Identifier; use crate::{ cmds, position_to_offset, span_contains, span_to_lsp_range, @@ -414,92 +415,114 @@ impl LanguageServer for Context { let mut symbols: Vec = Vec::new(); let uri = ¶ms.text_document.uri; - let Ok(path) = uri.to_file_path() else { - return Ok(None); - }; + let path = uri.to_file_path().ok(); - let loader = tx3_lang::loading::ProtocolLoader::from_file(&path); - let Ok(protocol) = loader.load() else { - return Ok(None); + let text = if let Some(document) = self.documents.get(uri) { + document.value().to_string() + } else if let Some(path) = path.as_ref() { + match std::fs::read_to_string(path) { + Ok(text) => text, + Err(_) => return Ok(Some(DocumentSymbolResponse::Nested(vec![]))), + } + } else { + return Ok(Some(DocumentSymbolResponse::Nested(vec![]))); }; - let Some(document) = self.documents.get(uri) else { - return Ok(None); + let rope = Rope::from_str(&text); + + let mut ast = match tx3_lang::parsing::parse_string(text.as_str()) { + Ok(ast) => ast, + Err(_) => return Ok(None), }; - let ast = protocol.ast(); - - if let Some(scope) = ast.scope() { - let mut current_scope = Some(scope); - while let Some(s) = current_scope { - for (name, symbol) in s.symbols() { - let (kind, range, detail) = match symbol { - Symbol::PartyDef(def) => ( - SymbolKind::OBJECT, - span_to_lsp_range(document.value(), &def.span), - "Party".to_string(), - ), - Symbol::PolicyDef(def) => ( - SymbolKind::KEY, - span_to_lsp_range(document.value(), &def.span), - "Policy".to_string(), - ), - Symbol::AssetDef(def) => ( - SymbolKind::FIELD, - span_to_lsp_range(document.value(), &def.span), - "Asset".to_string(), - ), - Symbol::TypeDef(def) => ( - SymbolKind::TYPE_PARAMETER, - span_to_lsp_range(document.value(), &def.span), - "Type".to_string(), - ), - Symbol::AliasDef(def) => ( - SymbolKind::INTERFACE, - span_to_lsp_range(document.value(), &def.span), - "Alias".to_string(), - ), - Symbol::RecordField(def) => ( - SymbolKind::FIELD, - span_to_lsp_range(document.value(), &def.span), - "Field".to_string(), - ), - Symbol::VariantCase(def) => ( - SymbolKind::ENUM_MEMBER, - span_to_lsp_range(document.value(), &def.span), - "Variant Case".to_string(), - ), - Symbol::EnvVar(_, ty) => ( - SymbolKind::VARIABLE, - Range::default(), - format!("EnvVar({:?})", ty), - ), - Symbol::ParamVar(_, ty) => ( - SymbolKind::VARIABLE, - Range::default(), - format!("ParamVar({:?})", ty), - ), - Symbol::LocalExpr(_) => ( - SymbolKind::VARIABLE, - Range::default(), - "LocalExpr".to_string(), - ), - Symbol::Output(idx) => ( - SymbolKind::OBJECT, - Range::default(), - format!("Output({})", idx), - ), - Symbol::Input(_) => { - (SymbolKind::OBJECT, Range::default(), "Input".to_string()) - } - Symbol::Fees => { - (SymbolKind::CONSTANT, Range::default(), "Fees".to_string()) - } - }; + // Track counts before import resolution + let local_types_count = ast.types.len(); + let local_aliases_count = ast.aliases.len(); - symbols.push(make_symbol(name.clone(), detail, kind, range, None)); - } - current_scope = s.parent(); + if let Some(root) = path.as_ref().and_then(|path| path.parent()) { + let loader = tx3_lang::importing::FsLoader::new(root); + let _ = tx3_lang::importing::resolve_imports(&mut ast, Some(&loader)); + } + + for party in &ast.parties { + symbols.push(make_symbol( + party.name.value.clone(), + "Party".to_string(), + SymbolKind::OBJECT, + span_to_lsp_range(&rope, &party.span), + None, + )); + } + + for policy in &ast.policies { + symbols.push(make_symbol( + policy.name.value.clone(), + "Policy".to_string(), + SymbolKind::KEY, + span_to_lsp_range(&rope, &policy.span), + None, + )); + } + + for asset in &ast.assets { + symbols.push(make_symbol( + asset.name.value.clone(), + "Asset".to_string(), + SymbolKind::FIELD, + span_to_lsp_range(&rope, &asset.span), + None, + )); + } + + // Local types + for (idx, type_def) in ast.types.iter().enumerate() { + if idx < local_types_count { + symbols.push(make_symbol( + type_def.name.value.clone(), + "Record".to_string(), + SymbolKind::TYPE_PARAMETER, + span_to_lsp_range(&rope, &type_def.span), + None, + )); + } + } + + // Imported types + for (idx, type_def) in ast.types.iter().enumerate() { + if idx >= local_types_count { + symbols.push(make_symbol( + type_def.name.value.clone(), + "Imported Type".to_string(), + SymbolKind::TYPE_PARAMETER, + span_to_lsp_range(&rope, &type_def.span), + None, + )); + } + } + + // Local aliases + for (idx, alias) in ast.aliases.iter().enumerate() { + if idx < local_aliases_count { + symbols.push(make_symbol( + alias.name.value.clone(), + "Alias".to_string(), + SymbolKind::INTERFACE, + span_to_lsp_range(&rope, &alias.span), + None, + )); + } + } + + // Imported aliases + for (idx, alias) in ast.aliases.iter().enumerate() { + if idx >= local_aliases_count { + symbols.push(make_symbol( + alias.name.value.clone(), + "Imported Alias".to_string(), + SymbolKind::INTERFACE, + span_to_lsp_range(&rope, &alias.span), + None, + )); } } @@ -510,7 +533,7 @@ impl LanguageServer for Context { parameter.name.value.clone(), format!("Parameter<{:?}>", parameter.r#type), SymbolKind::FIELD, - span_to_lsp_range(document.value(), &tx.parameters.span), + span_to_lsp_range(&rope, &tx.parameters.span), None, )); } @@ -520,7 +543,7 @@ impl LanguageServer for Context { input.name.clone(), "Input".to_string(), SymbolKind::OBJECT, - span_to_lsp_range(document.value(), &input.span), + span_to_lsp_range(&rope, &input.span), None, )); } @@ -534,7 +557,7 @@ impl LanguageServer for Context { name.value.clone(), "Output".to_string(), SymbolKind::OBJECT, - span_to_lsp_range(document.value(), &output.span), + span_to_lsp_range(&rope, &output.span), None, )); } @@ -543,7 +566,7 @@ impl LanguageServer for Context { tx.name.value.clone(), "Tx".to_string(), SymbolKind::METHOD, - span_to_lsp_range(document.value(), &tx.span), + span_to_lsp_range(&rope, &tx.span), Some(children), )); }