Skip to content

Commit 00c4651

Browse files
committed
Add ignore dependency
1 parent f7bd1bb commit 00c4651

File tree

4 files changed

+291
-2
lines changed

4 files changed

+291
-2
lines changed

Cargo.lock

Lines changed: 87 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ license = "MIT"
99

1010
[dependencies]
1111
clap = { version = "4.5.16", features = [ "wrap_help" ] }
12+
ignore = "0.4"
1213
lsp-server = "0.7.9"
1314
lsp-types = "0.97"
1415
owo-colors = "4"

src/editor/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ pub(crate) fn run_language_server() {
1414
let capabilities = serde_json::to_value(ServerCapabilities {
1515
text_document_sync: Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::FULL)),
1616
document_formatting_provider: Some(OneOf::Left(true)),
17+
document_symbol_provider: Some(OneOf::Left(true)),
18+
workspace_symbol_provider: Some(OneOf::Left(true)),
1719
..Default::default()
1820
})
1921
.unwrap();

src/editor/server.rs

Lines changed: 201 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ use lsp_server::{Connection, Message, Notification, Request, Response};
55
use lsp_types::{
66
Diagnostic, DiagnosticSeverity, DidChangeTextDocumentParams, DidCloseTextDocumentParams,
77
DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentFormattingParams,
8-
InitializeParams, InitializeResult, InitializedParams, Position, PublishDiagnosticsParams,
9-
Range, ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind, TextEdit, Uri,
8+
DocumentSymbolParams, DocumentSymbolResponse, InitializedParams, Location, Position,
9+
PublishDiagnosticsParams, Range, SymbolInformation, SymbolKind, TextEdit, Uri,
10+
WorkspaceSymbolParams,
1011
};
1112
use serde_json::{from_value, to_value, Value};
1213
use technique::formatting::Identity;
14+
use technique::language::{Document, Technique};
1315
use tracing::{debug, error, info, warn};
1416

1517
use crate::formatting;
@@ -99,6 +101,40 @@ impl TechniqueLanguageServer {
99101
}
100102
}
101103
}
104+
"textDocument/documentSymbol" => {
105+
let params: DocumentSymbolParams = from_value(req.params)?;
106+
match self.handle_document_symbol(params) {
107+
Ok(result) => {
108+
let response = Response::new_ok(req.id, result);
109+
sender(Message::Response(response))?;
110+
}
111+
Err(err) => {
112+
let response = Response::new_err(
113+
req.id,
114+
lsp_server::ErrorCode::InternalError as i32,
115+
err.to_string(),
116+
);
117+
sender(Message::Response(response))?;
118+
}
119+
}
120+
}
121+
"workspace/symbol" => {
122+
let params: WorkspaceSymbolParams = from_value(req.params)?;
123+
match self.handle_workspace_symbol(params) {
124+
Ok(result) => {
125+
let response = Response::new_ok(req.id, result);
126+
sender(Message::Response(response))?;
127+
}
128+
Err(err) => {
129+
let response = Response::new_err(
130+
req.id,
131+
lsp_server::ErrorCode::InternalError as i32,
132+
err.to_string(),
133+
);
134+
sender(Message::Response(response))?;
135+
}
136+
}
137+
}
102138
"shutdown" => {
103139
info!("Language Server received shutdown request");
104140
let response = Response::new_ok(req.id, Value::Null);
@@ -316,6 +352,131 @@ impl TechniqueLanguageServer {
316352
Ok(Some(vec![edit]))
317353
}
318354

355+
fn handle_document_symbol(
356+
&self,
357+
params: DocumentSymbolParams,
358+
) -> Result<DocumentSymbolResponse, Box<dyn std::error::Error + Sync + Send>> {
359+
let uri = params
360+
.text_document
361+
.uri;
362+
363+
debug!("Document symbol request: {:?}", uri);
364+
365+
// Get content from our documents map
366+
let content = match self
367+
.documents
368+
.get(&uri)
369+
{
370+
Some(content) => content,
371+
None => {
372+
return Ok(DocumentSymbolResponse::Flat(vec![]));
373+
}
374+
};
375+
376+
let path = Path::new(
377+
uri.path()
378+
.as_str(),
379+
);
380+
381+
// Parse document with recovery to get symbols even if there are errors
382+
let document = match parsing::parse_with_recovery(path, content) {
383+
Ok(document) => document,
384+
Err(_) => {
385+
// Return empty symbols if parsing fails completely
386+
return Ok(DocumentSymbolResponse::Flat(vec![]));
387+
}
388+
};
389+
390+
let symbols = self.extract_symbols_from_document(&uri, content, &document);
391+
Ok(DocumentSymbolResponse::Flat(symbols))
392+
}
393+
394+
fn handle_workspace_symbol(
395+
&self,
396+
params: WorkspaceSymbolParams,
397+
) -> Result<Option<Vec<SymbolInformation>>, Box<dyn std::error::Error + Sync + Send>> {
398+
let query = params
399+
.query
400+
.to_lowercase();
401+
debug!("Workspace symbol request: query={:?}", query);
402+
403+
let mut all_symbols = Vec::new();
404+
405+
// Search through all open documents
406+
for (uri, content) in &self.documents {
407+
let path = Path::new(
408+
uri.path()
409+
.as_str(),
410+
);
411+
412+
// Try to parse each document
413+
if let Ok(document) = parsing::parse_with_recovery(&path, content) {
414+
let symbols = self.extract_symbols_from_document(uri, content, &document);
415+
416+
// Filter symbols by query
417+
for symbol in symbols {
418+
if query.is_empty()
419+
|| symbol
420+
.name
421+
.to_lowercase()
422+
.contains(&query)
423+
{
424+
all_symbols.push(symbol);
425+
}
426+
}
427+
}
428+
}
429+
430+
Ok(Some(all_symbols))
431+
}
432+
433+
fn extract_symbols_from_document(
434+
&self,
435+
uri: &Uri,
436+
content: &str,
437+
document: &Document,
438+
) -> Vec<SymbolInformation> {
439+
let mut symbols = Vec::new();
440+
441+
if let Some(ref body) = document.body {
442+
match body {
443+
Technique::Procedures(procedures) => {
444+
for procedure in procedures {
445+
let name = procedure
446+
.name
447+
.0;
448+
449+
// Calculate the byte offset of the name using pointer arithmetic
450+
let offset = calculate_slice_offset(content, name).unwrap_or(0);
451+
let position = offset_to_position(content, offset);
452+
453+
#[allow(deprecated)]
454+
let symbol = SymbolInformation {
455+
name: name.to_string(),
456+
kind: SymbolKind::CONSTRUCTOR,
457+
tags: None,
458+
deprecated: None, // deprecated but still required, how annoying
459+
location: Location {
460+
uri: uri.clone(),
461+
range: Range {
462+
start: position,
463+
end: position,
464+
},
465+
},
466+
container_name: None,
467+
};
468+
symbols.push(symbol);
469+
}
470+
}
471+
_ => {
472+
// Steps or Empty - no symbols to extract
473+
}
474+
}
475+
}
476+
477+
symbols
478+
}
479+
319480
/// Parse document and convert errors to diagnostics
320481
fn parse_and_report<E>(
321482
&self,
@@ -531,6 +692,23 @@ impl TechniqueLanguageServer {
531692
}
532693
}
533694

695+
/// Calculate the byte offset of a substring within a parent string using
696+
/// pointer arithmetic.
697+
///
698+
/// Returns None if the substring is not actually part of the parent string,
699+
/// checking first to see if the substring pointer is actually within the
700+
/// bounds of the parent string.
701+
fn calculate_slice_offset(parent: &str, substring: &str) -> Option<usize> {
702+
let parent_ptr = parent.as_ptr() as usize;
703+
let substring_ptr = substring.as_ptr() as usize;
704+
705+
if substring_ptr >= parent_ptr && substring_ptr < parent_ptr + parent.len() {
706+
Some(substring_ptr - parent_ptr)
707+
} else {
708+
None
709+
}
710+
}
711+
534712
/// Convert byte offset to LSP Position
535713
fn offset_to_position(text: &str, offset: usize) -> Position {
536714
let line = calculate_line_number(text, offset) as u32;
@@ -542,6 +720,27 @@ fn offset_to_position(text: &str, offset: usize) -> Position {
542720
mod tests {
543721
use super::*;
544722

723+
#[test]
724+
fn test_calculate_str_offsets() {
725+
let parent = "hello world, this is a test";
726+
727+
// Test substring that is part of parent
728+
let substring = &parent[6..11]; // "world"
729+
assert_eq!(calculate_slice_offset(parent, substring), Some(6));
730+
731+
// Test substring at the beginning
732+
let substring = &parent[0..5]; // "hello"
733+
assert_eq!(calculate_slice_offset(parent, substring), Some(0));
734+
735+
// Test substring at the end
736+
let substring = &parent[23..27]; // "test"
737+
assert_eq!(calculate_slice_offset(parent, substring), Some(23));
738+
739+
// Test substring that is not part of parent
740+
let other = "not from parent";
741+
assert_eq!(calculate_slice_offset(parent, other), None);
742+
}
743+
545744
#[test]
546745
fn test_offset_to_position() {
547746
let text = "line 1\nline 2\nline 3";

0 commit comments

Comments
 (0)