collie-lsp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.collie.yml.example +144 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +10 -0
- data/LICENSE +21 -0
- data/README.md +273 -0
- data/Rakefile +8 -0
- data/collie-lsp.gemspec +36 -0
- data/exe/collie-lsp +9 -0
- data/lib/collie_lsp/collie_wrapper.rb +97 -0
- data/lib/collie_lsp/document_store.rb +66 -0
- data/lib/collie_lsp/handlers/code_action.rb +77 -0
- data/lib/collie_lsp/handlers/completion.rb +72 -0
- data/lib/collie_lsp/handlers/definition.rb +117 -0
- data/lib/collie_lsp/handlers/diagnostics.rb +64 -0
- data/lib/collie_lsp/handlers/document_symbol.rb +185 -0
- data/lib/collie_lsp/handlers/folding_range.rb +211 -0
- data/lib/collie_lsp/handlers/formatting.rb +55 -0
- data/lib/collie_lsp/handlers/hover.rb +104 -0
- data/lib/collie_lsp/handlers/references.rb +185 -0
- data/lib/collie_lsp/handlers/rename.rb +226 -0
- data/lib/collie_lsp/handlers/semantic_tokens.rb +302 -0
- data/lib/collie_lsp/handlers/workspace_symbol.rb +161 -0
- data/lib/collie_lsp/protocol/initialize.rb +58 -0
- data/lib/collie_lsp/protocol/shutdown.rb +25 -0
- data/lib/collie_lsp/protocol/text_document.rb +81 -0
- data/lib/collie_lsp/server.rb +104 -0
- data/lib/collie_lsp/version.rb +5 -0
- data/lib/collie_lsp.rb +25 -0
- data/vscode-extension/.gitignore +3 -0
- data/vscode-extension/.vscode/launch.json +17 -0
- data/vscode-extension/.vscode/tasks.json +14 -0
- data/vscode-extension/README.md +35 -0
- data/vscode-extension/package.json +49 -0
- data/vscode-extension/src/extension.ts +48 -0
- data/vscode-extension/test-grammar.y +42 -0
- data/vscode-extension/tsconfig.json +12 -0
- metadata +98 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CollieLsp
|
|
4
|
+
module Handlers
|
|
5
|
+
# Quick fixes for autocorrectable offenses
|
|
6
|
+
module CodeAction
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# Handle textDocument/codeAction request
|
|
10
|
+
# @param request [Hash] LSP request
|
|
11
|
+
# @param document_store [DocumentStore] Document store
|
|
12
|
+
# @param collie [CollieWrapper] Collie wrapper
|
|
13
|
+
# @param writer [Object] Response writer
|
|
14
|
+
def handle(request, document_store, collie, writer)
|
|
15
|
+
uri = request[:params][:textDocument][:uri]
|
|
16
|
+
range = request[:params][:range]
|
|
17
|
+
doc = document_store.get(uri)
|
|
18
|
+
|
|
19
|
+
unless doc
|
|
20
|
+
writer.write(id: request[:id], result: [])
|
|
21
|
+
return
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Get diagnostics in range
|
|
25
|
+
diagnostics = doc[:diagnostics].select do |diag|
|
|
26
|
+
in_range?(diag, range)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
code_actions = []
|
|
30
|
+
|
|
31
|
+
# Add "Fix all" action if there are any diagnostics
|
|
32
|
+
if diagnostics.any?
|
|
33
|
+
filename = uri.gsub(%r{^file://}, '')
|
|
34
|
+
corrected = collie.autocorrect(doc[:text], filename: filename)
|
|
35
|
+
|
|
36
|
+
code_actions << {
|
|
37
|
+
title: 'Fix all auto-correctable offenses',
|
|
38
|
+
kind: 'source.fixAll',
|
|
39
|
+
edit: {
|
|
40
|
+
changes: {
|
|
41
|
+
uri => [{
|
|
42
|
+
range: full_document_range(doc[:text]),
|
|
43
|
+
newText: corrected
|
|
44
|
+
}]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
writer.write(
|
|
51
|
+
id: request[:id],
|
|
52
|
+
result: code_actions
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Check if a diagnostic is within the given range
|
|
57
|
+
# @param diagnostic [Hash] LSP diagnostic
|
|
58
|
+
# @param range [Hash] LSP range
|
|
59
|
+
# @return [Boolean]
|
|
60
|
+
def in_range?(diagnostic, range)
|
|
61
|
+
diagnostic[:range][:start][:line] >= range[:start][:line] &&
|
|
62
|
+
diagnostic[:range][:end][:line] <= range[:end][:line]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Get the range covering the entire document
|
|
66
|
+
# @param text [String] Document text
|
|
67
|
+
# @return [Hash] LSP range
|
|
68
|
+
def full_document_range(text)
|
|
69
|
+
lines = text.lines.count
|
|
70
|
+
{
|
|
71
|
+
start: { line: 0, character: 0 },
|
|
72
|
+
end: { line: lines, character: 0 }
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CollieLsp
|
|
4
|
+
module Handlers
|
|
5
|
+
# Auto-completion for tokens and nonterminals
|
|
6
|
+
module Completion
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# Handle textDocument/completion request
|
|
10
|
+
# @param request [Hash] LSP request
|
|
11
|
+
# @param document_store [DocumentStore] Document store
|
|
12
|
+
# @param _collie [CollieWrapper] Collie wrapper (unused)
|
|
13
|
+
# @param writer [Object] Response writer
|
|
14
|
+
def handle(request, document_store, _collie, writer)
|
|
15
|
+
uri = request[:params][:textDocument][:uri]
|
|
16
|
+
_position = request[:params][:position]
|
|
17
|
+
doc = document_store.get(uri)
|
|
18
|
+
|
|
19
|
+
unless doc
|
|
20
|
+
writer.write(id: request[:id], result: [])
|
|
21
|
+
return
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
ast = doc[:ast]
|
|
25
|
+
unless ast
|
|
26
|
+
writer.write(id: request[:id], result: [])
|
|
27
|
+
return
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
completions = build_completions(ast)
|
|
31
|
+
|
|
32
|
+
writer.write(
|
|
33
|
+
id: request[:id],
|
|
34
|
+
result: completions
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Build completion items from AST
|
|
39
|
+
# @param ast [Hash] Parsed AST
|
|
40
|
+
# @return [Array<Hash>] LSP completion items
|
|
41
|
+
def build_completions(ast)
|
|
42
|
+
completions = []
|
|
43
|
+
|
|
44
|
+
# Add all declared tokens
|
|
45
|
+
ast[:declarations]&.each do |decl|
|
|
46
|
+
next unless decl[:kind] == :token
|
|
47
|
+
|
|
48
|
+
decl[:names]&.each do |name|
|
|
49
|
+
completions << {
|
|
50
|
+
label: name,
|
|
51
|
+
kind: 14, # Keyword
|
|
52
|
+
detail: "Token: #{name}",
|
|
53
|
+
documentation: 'Declared token'
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Add all nonterminals
|
|
59
|
+
ast[:rules]&.each do |rule|
|
|
60
|
+
completions << {
|
|
61
|
+
label: rule[:name],
|
|
62
|
+
kind: 7, # Class (nonterminal)
|
|
63
|
+
detail: "Nonterminal: #{rule[:name]}",
|
|
64
|
+
documentation: 'Grammar rule'
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
completions
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CollieLsp
|
|
4
|
+
module Handlers
|
|
5
|
+
# Go to definition support
|
|
6
|
+
module Definition
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# Handle textDocument/definition request
|
|
10
|
+
# @param request [Hash] LSP request
|
|
11
|
+
# @param document_store [DocumentStore] Document store
|
|
12
|
+
# @param _collie [CollieWrapper] Collie wrapper (unused)
|
|
13
|
+
# @param writer [Object] Response writer
|
|
14
|
+
def handle(request, document_store, _collie, writer)
|
|
15
|
+
uri = request[:params][:textDocument][:uri]
|
|
16
|
+
position = request[:params][:position]
|
|
17
|
+
doc = document_store.get(uri)
|
|
18
|
+
|
|
19
|
+
unless doc
|
|
20
|
+
writer.write(id: request[:id], result: nil)
|
|
21
|
+
return
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
ast = doc[:ast]
|
|
25
|
+
unless ast
|
|
26
|
+
writer.write(id: request[:id], result: nil)
|
|
27
|
+
return
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Find symbol at position
|
|
31
|
+
symbol = find_symbol_at_position(doc[:text], position)
|
|
32
|
+
unless symbol
|
|
33
|
+
writer.write(id: request[:id], result: nil)
|
|
34
|
+
return
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
location = find_definition_location(ast, symbol, uri)
|
|
38
|
+
|
|
39
|
+
if location
|
|
40
|
+
writer.write(id: request[:id], result: location)
|
|
41
|
+
else
|
|
42
|
+
writer.write(id: request[:id], result: nil)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Find symbol at the given position
|
|
47
|
+
# @param text [String] Document text
|
|
48
|
+
# @param position [Hash] LSP position
|
|
49
|
+
# @return [String, nil] Symbol name or nil
|
|
50
|
+
def find_symbol_at_position(text, position)
|
|
51
|
+
lines = text.lines
|
|
52
|
+
line = lines[position[:line]]
|
|
53
|
+
return nil unless line
|
|
54
|
+
|
|
55
|
+
# Extract word at character position
|
|
56
|
+
char = position[:character]
|
|
57
|
+
start_pos = char
|
|
58
|
+
end_pos = char
|
|
59
|
+
|
|
60
|
+
# Move backwards to find word start
|
|
61
|
+
start_pos -= 1 while start_pos.positive? && line[start_pos - 1] =~ /[A-Za-z0-9_]/
|
|
62
|
+
# Move forwards to find word end
|
|
63
|
+
end_pos += 1 while end_pos < line.length && line[end_pos] =~ /[A-Za-z0-9_]/
|
|
64
|
+
|
|
65
|
+
line[start_pos...end_pos]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Find definition location for a symbol
|
|
69
|
+
# @param ast [Hash] Parsed AST
|
|
70
|
+
# @param symbol [String] Symbol name
|
|
71
|
+
# @param uri [String] Document URI
|
|
72
|
+
# @return [Hash, nil] LSP location or nil
|
|
73
|
+
def find_definition_location(ast, symbol, uri)
|
|
74
|
+
# Check if it's a token declaration
|
|
75
|
+
ast[:declarations]&.each do |decl|
|
|
76
|
+
next unless decl[:kind] == :token
|
|
77
|
+
|
|
78
|
+
if decl[:names]&.include?(symbol) && decl[:location]
|
|
79
|
+
return {
|
|
80
|
+
uri: uri,
|
|
81
|
+
range: {
|
|
82
|
+
start: {
|
|
83
|
+
line: decl[:location][:line] - 1,
|
|
84
|
+
character: decl[:location][:column] - 1
|
|
85
|
+
},
|
|
86
|
+
end: {
|
|
87
|
+
line: decl[:location][:line] - 1,
|
|
88
|
+
character: decl[:location][:column] + symbol.length - 1
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Check if it's a nonterminal rule
|
|
96
|
+
rule = ast[:rules]&.find { |r| r[:name] == symbol }
|
|
97
|
+
if rule && rule[:location]
|
|
98
|
+
return {
|
|
99
|
+
uri: uri,
|
|
100
|
+
range: {
|
|
101
|
+
start: {
|
|
102
|
+
line: rule[:location][:line] - 1,
|
|
103
|
+
character: rule[:location][:column] - 1
|
|
104
|
+
},
|
|
105
|
+
end: {
|
|
106
|
+
line: rule[:location][:line] - 1,
|
|
107
|
+
character: rule[:location][:column] + symbol.length - 1
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
nil
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CollieLsp
|
|
4
|
+
module Handlers
|
|
5
|
+
# Converts Collie offenses to LSP diagnostics
|
|
6
|
+
module Diagnostics
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# Publish diagnostics for a document
|
|
10
|
+
# @param uri [String] Document URI
|
|
11
|
+
# @param offenses [Array<Hash>] Collie offenses
|
|
12
|
+
# @param document_store [DocumentStore] Document store
|
|
13
|
+
# @param writer [Object] Response writer
|
|
14
|
+
def publish(uri, offenses, document_store, writer)
|
|
15
|
+
diagnostics = offenses.map do |offense|
|
|
16
|
+
offense_to_diagnostic(offense)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
document_store.update_diagnostics(uri, diagnostics)
|
|
20
|
+
|
|
21
|
+
writer.write(
|
|
22
|
+
method: 'textDocument/publishDiagnostics',
|
|
23
|
+
params: {
|
|
24
|
+
uri: uri,
|
|
25
|
+
diagnostics: diagnostics
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Convert a Collie offense to an LSP diagnostic
|
|
31
|
+
# @param offense [Hash] Collie offense
|
|
32
|
+
# @return [Hash] LSP diagnostic
|
|
33
|
+
def offense_to_diagnostic(offense)
|
|
34
|
+
location = offense[:location] || { line: 1, column: 1 }
|
|
35
|
+
line = location[:line] - 1 # LSP is 0-indexed
|
|
36
|
+
column = location[:column] - 1
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
range: {
|
|
40
|
+
start: { line: line, character: column },
|
|
41
|
+
end: { line: line, character: column + 10 } # Approximate
|
|
42
|
+
},
|
|
43
|
+
severity: severity_to_lsp(offense[:severity]),
|
|
44
|
+
code: offense[:rule_name] || 'unknown',
|
|
45
|
+
source: 'collie',
|
|
46
|
+
message: offense[:message] || 'Unknown error'
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Convert Collie severity to LSP severity
|
|
51
|
+
# @param severity [Symbol] Collie severity (:error, :warning, :convention, :info)
|
|
52
|
+
# @return [Integer] LSP severity (1-4)
|
|
53
|
+
def severity_to_lsp(severity)
|
|
54
|
+
case severity
|
|
55
|
+
when :error then 1 # Error
|
|
56
|
+
when :warning then 2 # Warning
|
|
57
|
+
when :convention then 3 # Information
|
|
58
|
+
when :info then 4 # Hint
|
|
59
|
+
else 3
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CollieLsp
|
|
4
|
+
module Handlers
|
|
5
|
+
# Document symbol support for outline view
|
|
6
|
+
module DocumentSymbol
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# Handle textDocument/documentSymbol request
|
|
10
|
+
# @param request [Hash] LSP request
|
|
11
|
+
# @param document_store [DocumentStore] Document store
|
|
12
|
+
# @param _collie [CollieWrapper] Collie wrapper (unused)
|
|
13
|
+
# @param writer [Object] Response writer
|
|
14
|
+
def handle(request, document_store, _collie, writer)
|
|
15
|
+
uri = request[:params][:textDocument][:uri]
|
|
16
|
+
doc = document_store.get(uri)
|
|
17
|
+
|
|
18
|
+
unless doc
|
|
19
|
+
writer.write(id: request[:id], result: [])
|
|
20
|
+
return
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
ast = doc[:ast]
|
|
24
|
+
unless ast
|
|
25
|
+
writer.write(id: request[:id], result: [])
|
|
26
|
+
return
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
symbols = build_document_symbols(ast)
|
|
30
|
+
|
|
31
|
+
writer.write(
|
|
32
|
+
id: request[:id],
|
|
33
|
+
result: symbols
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Build document symbols from AST
|
|
38
|
+
# @param ast [Hash] Parsed AST
|
|
39
|
+
# @return [Array<Hash>] LSP document symbols
|
|
40
|
+
def build_document_symbols(ast)
|
|
41
|
+
symbols = []
|
|
42
|
+
|
|
43
|
+
symbols.concat(build_token_symbols(ast))
|
|
44
|
+
symbols.concat(build_type_symbols(ast))
|
|
45
|
+
symbols.concat(build_precedence_symbols(ast))
|
|
46
|
+
symbols.concat(build_rule_symbols(ast))
|
|
47
|
+
|
|
48
|
+
symbols
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Build token symbols
|
|
52
|
+
# @param ast [Hash] Parsed AST
|
|
53
|
+
# @return [Array<Hash>] Token symbols
|
|
54
|
+
def build_token_symbols(ast)
|
|
55
|
+
symbols = []
|
|
56
|
+
ast[:declarations]&.each do |decl|
|
|
57
|
+
next unless decl[:kind] == :token && decl[:location]
|
|
58
|
+
|
|
59
|
+
decl[:names]&.each do |name|
|
|
60
|
+
symbols << create_symbol(
|
|
61
|
+
name: name,
|
|
62
|
+
kind: 14,
|
|
63
|
+
location: decl[:location],
|
|
64
|
+
detail: 'Token'
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
symbols
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Build type symbols
|
|
72
|
+
# @param ast [Hash] Parsed AST
|
|
73
|
+
# @return [Array<Hash>] Type symbols
|
|
74
|
+
def build_type_symbols(ast)
|
|
75
|
+
symbols = []
|
|
76
|
+
ast[:declarations]&.each do |decl|
|
|
77
|
+
next unless decl[:kind] == :type && decl[:location]
|
|
78
|
+
|
|
79
|
+
decl[:names]&.each do |name|
|
|
80
|
+
symbols << create_symbol(
|
|
81
|
+
name: name,
|
|
82
|
+
kind: 7,
|
|
83
|
+
location: decl[:location],
|
|
84
|
+
detail: 'Type'
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
symbols
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Build precedence symbols
|
|
92
|
+
# @param ast [Hash] Parsed AST
|
|
93
|
+
# @return [Array<Hash>] Precedence symbols
|
|
94
|
+
def build_precedence_symbols(ast)
|
|
95
|
+
symbols = []
|
|
96
|
+
ast[:declarations]&.each do |decl|
|
|
97
|
+
next unless %i[left right nonassoc].include?(decl[:kind]) && decl[:location]
|
|
98
|
+
|
|
99
|
+
assoc_name = decl[:kind].to_s.capitalize
|
|
100
|
+
decl[:tokens]&.each do |token|
|
|
101
|
+
symbols << create_symbol(
|
|
102
|
+
name: token,
|
|
103
|
+
kind: 22,
|
|
104
|
+
location: decl[:location],
|
|
105
|
+
detail: "#{assoc_name} precedence"
|
|
106
|
+
)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
symbols
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Build rule symbols
|
|
113
|
+
# @param ast [Hash] Parsed AST
|
|
114
|
+
# @return [Array<Hash>] Rule symbols
|
|
115
|
+
def build_rule_symbols(ast)
|
|
116
|
+
symbols = []
|
|
117
|
+
ast[:rules]&.each do |rule|
|
|
118
|
+
next unless rule[:location]
|
|
119
|
+
|
|
120
|
+
children = build_rule_children(rule)
|
|
121
|
+
symbols << create_symbol(
|
|
122
|
+
name: rule[:name],
|
|
123
|
+
kind: 12,
|
|
124
|
+
location: rule[:location],
|
|
125
|
+
detail: "Grammar rule (#{rule[:alternatives]&.size || 0} alternatives)",
|
|
126
|
+
children: children
|
|
127
|
+
)
|
|
128
|
+
end
|
|
129
|
+
symbols
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Build children symbols for a rule (alternatives)
|
|
133
|
+
# @param rule [Hash] Grammar rule
|
|
134
|
+
# @return [Array<Hash>] Child symbols
|
|
135
|
+
def build_rule_children(rule)
|
|
136
|
+
children = []
|
|
137
|
+
|
|
138
|
+
rule[:alternatives]&.each_with_index do |alt, index|
|
|
139
|
+
next unless alt[:location]
|
|
140
|
+
|
|
141
|
+
# Create a symbol for each alternative
|
|
142
|
+
symbols_str = alt[:symbols]&.map { |s| s[:name] }&.join(' ') || 'ε'
|
|
143
|
+
children << create_symbol(
|
|
144
|
+
name: "Alternative #{index + 1}",
|
|
145
|
+
kind: 6, # Property
|
|
146
|
+
location: alt[:location],
|
|
147
|
+
detail: symbols_str
|
|
148
|
+
)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
children
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Create a document symbol
|
|
155
|
+
# @param name [String] Symbol name
|
|
156
|
+
# @param kind [Integer] LSP symbol kind
|
|
157
|
+
# @param location [Hash] Location hash with :line and :column
|
|
158
|
+
# @param detail [String] Symbol detail
|
|
159
|
+
# @param children [Array<Hash>] Child symbols
|
|
160
|
+
# @return [Hash] LSP document symbol
|
|
161
|
+
def create_symbol(name:, kind:, location:, detail: nil, children: nil)
|
|
162
|
+
line = location[:line] - 1
|
|
163
|
+
column = location[:column] - 1
|
|
164
|
+
|
|
165
|
+
symbol = {
|
|
166
|
+
name: name,
|
|
167
|
+
kind: kind,
|
|
168
|
+
range: {
|
|
169
|
+
start: { line: line, character: column },
|
|
170
|
+
end: { line: line, character: column + name.length }
|
|
171
|
+
},
|
|
172
|
+
selectionRange: {
|
|
173
|
+
start: { line: line, character: column },
|
|
174
|
+
end: { line: line, character: column + name.length }
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
symbol[:detail] = detail if detail
|
|
179
|
+
symbol[:children] = children if children && !children.empty?
|
|
180
|
+
|
|
181
|
+
symbol
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|