Skip to content

Commit

Permalink
merlin folding ranges
Browse files Browse the repository at this point in the history
  • Loading branch information
dfgordon committed Aug 4, 2024
1 parent bd95d67 commit 00cac79
Show file tree
Hide file tree
Showing 21 changed files with 261 additions and 28 deletions.
4 changes: 4 additions & 0 deletions src/bin/server-merlin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ struct AnalysisResult {
uri: lsp::Url,
version: Option<i32>,
diagnostics: Vec<lsp::Diagnostic>,
folding: Vec<lsp::FoldingRange>,
symbols: merlin::Symbols,
workspace: merlin::Workspace
}
Expand Down Expand Up @@ -144,6 +145,7 @@ fn launch_analysis_thread(analyzer: Arc<Mutex<Analyzer>>, doc: a2kit::lang::Docu
uri: doc.uri.clone(),
version: doc.version,
diagnostics: analyzer.get_diags(&doc),
folding: analyzer.get_folds(&doc),
symbols: analyzer.get_symbols(),
workspace: analyzer.get_workspace().clone()
}),
Expand Down Expand Up @@ -280,6 +282,7 @@ fn main() -> Result<(), Box<dyn Error + Sync + Send>> {
first_trigger_character: " ".to_string(),
more_trigger_character: Some(vec![";".to_string()])
}),
folding_range_provider: Some(lsp::FoldingRangeProviderCapability::Simple(true)),
..lsp::ServerCapabilities::default()
},
server_info: Some(lsp::ServerInfo {
Expand Down Expand Up @@ -341,6 +344,7 @@ fn main() -> Result<(), Box<dyn Error + Sync + Send>> {
if let Some(chkpt) = tools.doc_chkpts.get_mut(&result.uri.to_string()) {
update_client_toolbar(&connection, &result.symbols).expect("toolbar update failed");
chkpt.update_symbols(result.symbols);
chkpt.update_folding_ranges(result.folding);
tools.hover_provider.use_shared_symbols(chkpt.shared_symbols());
tools.completion_provider.use_shared_symbols(chkpt.shared_symbols());
tools.tokenizer.use_shared_symbols(chkpt.shared_symbols());
Expand Down
1 change: 1 addition & 0 deletions src/bin/server-merlin/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub fn handle_request(
lsp::request::Rename::METHOD => Checkpoint::rename_response(chkpts, req.clone(), &mut resp),
lsp::request::HoverRequest::METHOD => Checkpoint::hover_response(chkpts, &mut tools.hover_provider, req.clone(), &mut resp),
lsp::request::Completion::METHOD => Checkpoint::completion_response(chkpts, &mut tools.completion_provider, req.clone(), &mut resp),
lsp::request::FoldingRangeRequest::METHOD => Checkpoint::folding_range_response(chkpts, req.clone(), &mut resp),

lsp::request::Shutdown::METHOD => {
logger(&connection,"shutdown request");
Expand Down
3 changes: 3 additions & 0 deletions src/lang/applesoft/checkpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ impl Checkpoint for CheckpointManager {
}
None
}
fn get_folding_ranges(&self) -> Vec<lsp_types::FoldingRange> {
Vec::new()
}
fn get_symbols(&self) -> Vec<lsp::DocumentSymbol> {
let sym = &self.symbols;
let mut ans = Vec::new();
Expand Down
3 changes: 3 additions & 0 deletions src/lang/applesoft/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ impl Analysis for Analyzer {
fn get_diags(&self,_doc: &Document) -> Vec<lsp::Diagnostic> {
self.diagnostics.clone()
}
fn get_folds(&self,_doc: &crate::lang::Document) -> Vec<lsp_types::FoldingRange> {
Vec::new()
}
fn err_warn_info_counts(&self) -> [usize;3] {
let mut err = 0;
let mut warn = 0;
Expand Down
3 changes: 3 additions & 0 deletions src/lang/integer/checkpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ impl Checkpoint for CheckpointManager {
}
None
}
fn get_folding_ranges(&self) -> Vec<lsp_types::FoldingRange> {
Vec::new()
}
fn get_symbols(&self) -> Vec<lsp::DocumentSymbol> {
let sym = &self.symbols;
let mut ans = Vec::new();
Expand Down
3 changes: 3 additions & 0 deletions src/lang/integer/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ impl Analysis for Analyzer {
fn get_diags(&self,_doc: &Document) -> Vec<Diagnostic> {
self.diagnostics.clone()
}
fn get_folds(&self,_doc: &crate::lang::Document) -> Vec<lsp_types::FoldingRange> {
Vec::new()
}
fn err_warn_info_counts(&self) -> [usize;3] {
let mut err = 0;
let mut warn = 0;
Expand Down
12 changes: 10 additions & 2 deletions src/lang/merlin/checkpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ use crate::lang::server::Checkpoint;

pub struct CheckpointManager {
doc: Document,
symbols: Arc<Symbols>
symbols: Arc<Symbols>,
folding_ranges: Vec<lsp::FoldingRange>
}

fn goto_defs(ans: &mut Vec<lsp::Location>,sel_loc: &lsp::Location,refs: &Vec<lsp::Location>,defs: &Vec<lsp::Location>) -> bool {
Expand Down Expand Up @@ -74,6 +75,9 @@ impl Checkpoint for CheckpointManager {
}
None
}
fn get_folding_ranges(&self) -> Vec<lsp_types::FoldingRange> {
self.folding_ranges.clone()
}
fn get_symbols(&self) -> Vec<lsp::DocumentSymbol> {
let sym = &self.symbols;
let mut ans = Vec::new();
Expand Down Expand Up @@ -199,7 +203,8 @@ impl CheckpointManager {
pub fn new() -> Self {
Self {
doc: Document::from_string("".to_string(),0),
symbols: Arc::new(Symbols::new())
symbols: Arc::new(Symbols::new()),
folding_ranges: Vec::new()
}
}
pub fn update_doc(&mut self,uri: lsp::Url, txt: String, version: Option<i32>) {
Expand All @@ -210,6 +215,9 @@ impl CheckpointManager {
pub fn update_symbols(&mut self,sym: Symbols) {
self.symbols = Arc::new(sym);
}
pub fn update_folding_ranges(&mut self,folding_ranges: Vec<lsp::FoldingRange>) {
self.folding_ranges = folding_ranges;
}
pub fn shared_symbols(&self) -> Arc<Symbols> {
Arc::clone(&self.symbols)
}
Expand Down
140 changes: 138 additions & 2 deletions src/lang/merlin/diagnostics/context.rs → src/lang/merlin/context.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,37 @@
//! Merlin Context module used for analysis and assembly
//!
//! This module manages containment relationships, builds docstrings, and owns a copy of the
//! assembler handbook. There are three containment stacks:
//!
//! 1. Scope stack - stack of `Symbol` structures, globals and macros define a scope
//! 2. Source stack - stack of `Source` structures, such as (Master (Use) (Put) (Put))
//! 3. Folding stack - stack of `Fold` structures, such as (DO (IF (ELSE (LUP))))
//!
//! In Merlin these are allowed to overlap, e.g., a fold could start in a PUT file
//! and end in the master file. The LSP does not allow this, so in such cases we
//! have to decline to report the fold to the client, but we can push a diagnostic warning.

use std::sync::Arc;
use lsp_types as lsp;
use tree_sitter::TreeCursor;
use super::super::{Symbol,Symbols,Workspace,MerlinVersion,symbol_flags,ProcessorType,SourceType};
use super::super::settings::Settings;
use crate::lang::merlin::{Symbol,Symbols,Workspace,MerlinVersion,symbol_flags,ProcessorType,SourceType};
use crate::lang::merlin::settings::Settings;
use crate::lang::merlin::handbook::operations::OperationHandbook;
use crate::lang::merlin::handbook::pseudo_ops::PseudoOperationHandbook;
use crate::lang::{Document,node_text,lsp_range};
use crate::lang::server::basic_diag;

#[derive(Clone)]
pub struct Fold {
/// syntax node kind that started this fold
pub kind: String,
/// value of pseudo-op argument that started the fold
pub arg: i64,
/// whether or not to assemble inside this fold
pub asm: bool,
/// start of the fold
pub start: lsp::Location,
}

#[derive(Clone)]
pub struct Source {
Expand All @@ -25,6 +51,8 @@ pub struct Context {
symbol_stack: Vec<Symbol>,
/// stack of document info for include descents
source_stack: Vec<Source>,
/// stack of folding ranges for conditionals and loops
fold_stack: Vec<Fold>,
/// built and consumed/cleared as lines are processed
pub running_docstring: String
}
Expand All @@ -51,6 +79,7 @@ impl Context {
xc_count: 0,
symbol_stack: Vec::new(),
source_stack: Vec::new(),
fold_stack: Vec::new(),
running_docstring: String::new()
}
}
Expand Down Expand Up @@ -128,6 +157,113 @@ impl Context {
pub fn exit_source(&mut self) -> Option<Source> {
self.source_stack.pop()
}
/// Push a folding range onto the source stack, kind is the syntax tree node kind.
/// Panics if the kind is unknown. Errors are detected by examining the returned
/// diagnostic, if any.
pub fn enter_folding_range(&mut self,kind: &str,rng: lsp::Range,loc: lsp::Location,arg: i64) -> Option<lsp::Diagnostic> {
let mut ans: Option<lsp::Diagnostic> = None;
let asm = match kind {
"psop_do" => arg != 0,
"psop_if" => arg != 0,
"psop_else" => {
let d1 = basic_diag(rng,"unmatched ELSE",lsp::DiagnosticSeverity::ERROR);
let d2 = basic_diag(rng, "multiple ELSE sections",lsp::DiagnosticSeverity::WARNING);
if let Some(parent) = self.fold_stack.last() {
if parent.kind=="psop_do" || parent.kind=="psop_if" {
!parent.asm
} else if parent.kind=="psop_else" {
ans = Some(d2);
!parent.asm
} else {
ans = Some(d1);
true
}
} else {
ans = Some(d1);
true
}
},
"psop_lup" => {
if let Some(parent) = self.fold_stack.last() {
parent.asm
} else {
true
}
},
_ => panic!("unexpected folding range kind")
};
if let Some(diag) = &ans {
if let Some(sev) = diag.severity {
if sev == lsp::DiagnosticSeverity::ERROR {
return ans;
}
}
}
self.fold_stack.push(Fold {
kind: kind.to_string(),
arg,
asm,
start: loc
});
ans
}
/// Exit the folding range, kind is the syntax tree node kind.
/// Panics if the kind is unknown. If there is an error a String is returned with the
/// diagnostic message. Handles FIN and --^. This should not be called with ELSE.
pub fn exit_folding_range(&mut self, kind: &str, rng: lsp::Range, loc: lsp::Location) -> Result<Option<lsp::FoldingRange>,lsp::Diagnostic> {
let start_loc = match kind {
"psop_fin" => {
let d1 = basic_diag(rng, "unmatched FIN",lsp::DiagnosticSeverity::ERROR);
let mut strip_else = true;
while strip_else {
if let Some(parent) = self.fold_stack.last() {
if parent.kind=="psop_else" {
self.fold_stack.pop();
} else {
strip_else = false;
}
} else {
strip_else = false;
}
}
if let Some(parent) = self.fold_stack.last() {
if parent.kind == "psop_lup" {
return Err(d1)
} else {
parent.start.clone()
}
} else {
return Err(d1)
}
},
"psop_end_lup" => {
let d1 = basic_diag(rng, "unmatched end of loop",lsp::DiagnosticSeverity::ERROR);
if let Some(parent) = self.fold_stack.last() {
if parent.kind == "psop_lup" {
parent.start.clone()
} else {
return Err(d1)
}
} else {
return Err(d1)
}
},
_ => panic!("unexpected folding range terminator")
};
self.fold_stack.pop();
if start_loc.uri != loc.uri {
let d1 = basic_diag(rng, "start of fold was in another document",lsp::DiagnosticSeverity::WARNING);
return Err(d1);
}
Ok(Some(lsp::FoldingRange {
start_line: start_loc.range.start.line,
end_line: loc.range.start.line,
start_character: None,
end_character: None,
kind: None,
collapsed_text: None
}))
}
/// advance the row in the current source strings
pub fn next_row(&mut self) {
if let Some(src) = self.source_stack.last_mut() {
Expand Down
2 changes: 1 addition & 1 deletion src/lang/merlin/diagnostics/addressing.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

use lsp_types as lsp;
use tree_sitter::TreeCursor;
use super::context::Context;
use crate::lang::merlin::context::Context;
use super::super::ProcessorType;

pub struct AddressModeSentry {
Expand Down
2 changes: 1 addition & 1 deletion src/lang/merlin/diagnostics/labels.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::HashMap;
use lsp_types as lsp;
use tree_sitter::TreeCursor;
use super::context::Context;
use crate::lang::merlin::context::Context;
use super::super::{Symbol,Symbols,Workspace,SourceType,LabelType};
use super::super::symbol_flags as flg;
use crate::lang::merlin::{self, MerlinVersion};
Expand Down
4 changes: 2 additions & 2 deletions src/lang/merlin/diagnostics/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ use std::collections::HashSet;
use lsp_types as lsp;
use crate::lang::{Navigate,Navigation};
use crate::lang::server::basic_diag;
use super::super::Symbols;
use crate::lang::merlin::Symbols;
use crate::lang::{node_text,lsp_range};
use super::context::Context;
use crate::lang::merlin::context::Context;
use crate::DYNERR;

struct Substitutor {
Expand Down
Loading

0 comments on commit 00cac79

Please sign in to comment.