kapusta 0.7.0 → 0.8.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 +4 -4
- data/bin/fennel-parity +9 -4
- data/examples/import-helpers.kapm +9 -0
- data/examples/macros-import-helpers.kap +3 -0
- data/examples/macros-import-whole.kap +5 -0
- data/examples/macros-import.kap +6 -0
- data/examples/shared-macros.kapm +4 -0
- data/lib/kapusta/compiler/macro_expander.rb +54 -142
- data/lib/kapusta/compiler/macro_gensym.rb +21 -0
- data/lib/kapusta/compiler/macro_importer.rb +81 -0
- data/lib/kapusta/compiler/macro_lowerer.rb +184 -0
- data/lib/kapusta/errors.rb +6 -1
- data/lib/kapusta/lsp/definition.rb +67 -0
- data/lib/kapusta/lsp/diagnostics.rb +42 -0
- data/lib/kapusta/lsp/formatting.rb +30 -0
- data/lib/kapusta/lsp/identifier.rb +28 -0
- data/lib/kapusta/lsp/rename.rb +417 -0
- data/lib/kapusta/lsp/scope_walker.rb +643 -0
- data/lib/kapusta/lsp/workspace_index.rb +225 -0
- data/lib/kapusta/lsp.rb +102 -48
- data/lib/kapusta/version.rb +1 -1
- data/spec/examples_errors_spec.rb +17 -1
- data/spec/examples_spec.rb +12 -0
- data/spec/lsp_spec.rb +535 -15
- metadata +16 -1
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
require_relative '../reader'
|
|
5
|
+
require_relative 'scope_walker'
|
|
6
|
+
|
|
7
|
+
module Kapusta
|
|
8
|
+
class LSP
|
|
9
|
+
class WorkspaceIndex
|
|
10
|
+
Entry = Struct.new(:uri, :text, :forms, :walker, keyword_init: true)
|
|
11
|
+
|
|
12
|
+
MACRO_MODULE_EXTENSIONS = %w[kapm kap].freeze
|
|
13
|
+
SCAN_EXTENSIONS = %w[kap kapm].freeze
|
|
14
|
+
|
|
15
|
+
def initialize(roots: [])
|
|
16
|
+
@roots = Array(roots)
|
|
17
|
+
@entries = {}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def scan!
|
|
21
|
+
@roots.each do |root|
|
|
22
|
+
SCAN_EXTENSIONS.each do |ext|
|
|
23
|
+
Dir.glob(File.join(root, '**', "*.#{ext}")).each do |path|
|
|
24
|
+
uri = path_to_uri(path)
|
|
25
|
+
text = File.read(path)
|
|
26
|
+
store(uri, text)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def refresh(uri, text)
|
|
34
|
+
store(uri, text)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def remove(uri)
|
|
38
|
+
path = uri_to_path(uri)
|
|
39
|
+
if path && File.file?(path)
|
|
40
|
+
store(uri, File.read(path))
|
|
41
|
+
else
|
|
42
|
+
@entries.delete(uri)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def entry(uri)
|
|
47
|
+
@entries[uri]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def entry_count
|
|
51
|
+
@entries.length
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def toplevel_fn_definitions(name)
|
|
55
|
+
result = []
|
|
56
|
+
@entries.each do |uri, entry|
|
|
57
|
+
entry.walker.bindings.each do |b|
|
|
58
|
+
result << [uri, b] if b.kind == :toplevel_fn && b.name == name
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
result
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def constant_definitions_with_prefix(prefix)
|
|
65
|
+
result = []
|
|
66
|
+
@entries.each do |uri, entry|
|
|
67
|
+
entry.walker.bindings.each do |b|
|
|
68
|
+
next unless %i[module class].include?(b.kind)
|
|
69
|
+
|
|
70
|
+
segs = b.sym.dotted? ? b.sym.segments : [b.sym.name]
|
|
71
|
+
result << [uri, b] if segs == prefix
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
result
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def toplevel_fn_occurrences(name)
|
|
78
|
+
result = {}
|
|
79
|
+
@entries.each do |uri, entry|
|
|
80
|
+
occs = entry.walker.bindings.select do |b|
|
|
81
|
+
b.kind == :toplevel_fn && b.name == name
|
|
82
|
+
end
|
|
83
|
+
occs += entry.walker.references.select do |r|
|
|
84
|
+
next false unless r.sym.is_a?(Sym) && !r.sym.dotted? && r.name == name
|
|
85
|
+
|
|
86
|
+
r.target.nil? || (r.target.kind == :toplevel_fn && r.target.name == name)
|
|
87
|
+
end
|
|
88
|
+
result[uri] = occs unless occs.empty?
|
|
89
|
+
end
|
|
90
|
+
result
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def toplevel_definition?(name, except_name: nil)
|
|
94
|
+
@entries.any? do |_uri, entry|
|
|
95
|
+
entry.walker.bindings.any? do |b|
|
|
96
|
+
next false unless file_toplevel_binding?(b)
|
|
97
|
+
next false if except_name && b.name == except_name
|
|
98
|
+
|
|
99
|
+
b.name == name
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def constant_definition_with_prefix?(prefix, except_prefix: nil)
|
|
105
|
+
@entries.any? do |_uri, entry|
|
|
106
|
+
entry.walker.bindings.any? do |b|
|
|
107
|
+
next false unless %i[module class].include?(b.kind)
|
|
108
|
+
next false if except_prefix && matches_prefix?(b.sym, except_prefix)
|
|
109
|
+
|
|
110
|
+
matches_prefix?(b.sym, prefix)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def each_entry(&)
|
|
116
|
+
@entries.each(&)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def find_macro_definition(importing_uri, module_label, import_key)
|
|
120
|
+
target_name = import_key.to_s.tr('_', '-')
|
|
121
|
+
resolve_module_uris(importing_uri, module_label).each do |uri|
|
|
122
|
+
entry = @entries[uri]
|
|
123
|
+
next unless entry
|
|
124
|
+
|
|
125
|
+
binding = entry.walker.bindings.find do |b|
|
|
126
|
+
%i[macro toplevel_fn].include?(b.kind) && b.name == target_name
|
|
127
|
+
end
|
|
128
|
+
return [uri, binding] if binding
|
|
129
|
+
end
|
|
130
|
+
nil
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def import_resolves_to?(importing_uri, module_label, target_uri)
|
|
134
|
+
resolve_module_uris(importing_uri, module_label).include?(target_uri)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def macro_definition_anywhere?(name, except_uri: nil)
|
|
138
|
+
@entries.any? do |uri, entry|
|
|
139
|
+
next false if except_uri && uri == except_uri
|
|
140
|
+
|
|
141
|
+
entry.walker.bindings.any? { |b| b.kind == :macro && b.name == name }
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def constant_occurrences(prefix)
|
|
146
|
+
result = {}
|
|
147
|
+
@entries.each do |uri, entry|
|
|
148
|
+
occs = []
|
|
149
|
+
entry.walker.bindings.each do |b|
|
|
150
|
+
next unless %i[module class].include?(b.kind)
|
|
151
|
+
|
|
152
|
+
occs << b if matches_prefix?(b.sym, prefix)
|
|
153
|
+
end
|
|
154
|
+
entry.walker.references.each do |r|
|
|
155
|
+
sym = r.sym
|
|
156
|
+
next unless sym.is_a?(Sym)
|
|
157
|
+
next unless r.target.nil?
|
|
158
|
+
next unless first_segment_capitalized?(sym)
|
|
159
|
+
|
|
160
|
+
occs << r if matches_prefix?(sym, prefix)
|
|
161
|
+
end
|
|
162
|
+
result[uri] = occs unless occs.empty?
|
|
163
|
+
end
|
|
164
|
+
result
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
private
|
|
168
|
+
|
|
169
|
+
def file_toplevel_binding?(binding)
|
|
170
|
+
binding.scope.kind == :file && %i[toplevel_fn local var].include?(binding.kind)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def matches_prefix?(sym, prefix)
|
|
174
|
+
segs = sym.dotted? ? sym.segments : [sym.name]
|
|
175
|
+
return false if segs.length < prefix.length
|
|
176
|
+
|
|
177
|
+
segs[0...prefix.length] == prefix
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def resolve_module_uris(importing_uri, module_label)
|
|
181
|
+
importing_path = uri_to_path(importing_uri)
|
|
182
|
+
return [] unless importing_path
|
|
183
|
+
|
|
184
|
+
base_dir = File.dirname(importing_path)
|
|
185
|
+
snake_stem = module_label.to_s.tr('-', '_')
|
|
186
|
+
kebab_stem = module_label.to_s.tr('_', '-')
|
|
187
|
+
uris = []
|
|
188
|
+
[kebab_stem, snake_stem].uniq.each do |stem|
|
|
189
|
+
MACRO_MODULE_EXTENSIONS.each do |ext|
|
|
190
|
+
uris << path_to_uri(File.expand_path("#{stem}.#{ext}", base_dir))
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
uris
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def first_segment_capitalized?(sym)
|
|
197
|
+
first = sym.dotted? ? sym.segments.first : sym.name
|
|
198
|
+
first.match?(/\A[A-Z]/)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def store(uri, text)
|
|
202
|
+
forms = Reader.read_all(text)
|
|
203
|
+
walker = ScopeWalker.analyze(forms)
|
|
204
|
+
@entries[uri] = Entry.new(uri:, text:, forms:, walker:)
|
|
205
|
+
rescue Kapusta::Error
|
|
206
|
+
@entries[uri] = Entry.new(uri:, text:, forms: [], walker: ScopeWalker.analyze([]))
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def path_to_uri(path)
|
|
210
|
+
"file://#{URI::DEFAULT_PARSER.escape(File.expand_path(path))}"
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def uri_to_path(uri)
|
|
214
|
+
return unless uri.is_a?(String)
|
|
215
|
+
|
|
216
|
+
parsed = URI.parse(uri)
|
|
217
|
+
return URI::DEFAULT_PARSER.unescape(parsed.path) if parsed.scheme == 'file'
|
|
218
|
+
|
|
219
|
+
uri
|
|
220
|
+
rescue URI::InvalidURIError
|
|
221
|
+
nil
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
data/lib/kapusta/lsp.rb
CHANGED
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
require 'json'
|
|
4
4
|
require 'uri'
|
|
5
5
|
require_relative '../kapusta'
|
|
6
|
-
require_relative '
|
|
6
|
+
require_relative 'lsp/definition'
|
|
7
|
+
require_relative 'lsp/diagnostics'
|
|
8
|
+
require_relative 'lsp/formatting'
|
|
9
|
+
require_relative 'lsp/rename'
|
|
10
|
+
require_relative 'lsp/workspace_index'
|
|
7
11
|
|
|
8
12
|
module Kapusta
|
|
9
13
|
class LSP
|
|
10
14
|
NOT_INITIALIZED = -32_002
|
|
11
15
|
METHOD_NOT_FOUND = -32_601
|
|
12
|
-
SEVERITY_ERROR = 1
|
|
13
16
|
FULL_SYNC = 1
|
|
14
17
|
|
|
15
18
|
def self.start(input: $stdin, output: $stdout, log: $stderr)
|
|
@@ -20,7 +23,9 @@ module Kapusta
|
|
|
20
23
|
@input = input.binmode
|
|
21
24
|
@output = output.binmode
|
|
22
25
|
@log = log
|
|
26
|
+
@debug = %w[1 true yes on].include?(ENV['KAPUSTA_LS_DEBUG'].to_s.downcase)
|
|
23
27
|
@sources = {}
|
|
28
|
+
@workspace_index = WorkspaceIndex.new
|
|
24
29
|
@initialized = false
|
|
25
30
|
@shutdown = false
|
|
26
31
|
end
|
|
@@ -94,6 +99,7 @@ module Kapusta
|
|
|
94
99
|
def dispatch(method, id, params)
|
|
95
100
|
case method
|
|
96
101
|
when 'initialize'
|
|
102
|
+
on_initialize(params)
|
|
97
103
|
@initialized = true
|
|
98
104
|
reply(id, initialize_result)
|
|
99
105
|
when 'initialized' then nil
|
|
@@ -106,6 +112,9 @@ module Kapusta
|
|
|
106
112
|
when 'textDocument/didSave' then on_did_save(params)
|
|
107
113
|
when 'textDocument/didClose' then on_did_close(params)
|
|
108
114
|
when 'textDocument/formatting' then reply(id, formatting(params))
|
|
115
|
+
when 'textDocument/definition' then reply(id, definition(params))
|
|
116
|
+
when 'textDocument/prepareRename' then reply(id, prepare_rename(params))
|
|
117
|
+
when 'textDocument/rename' then handle_rename(id, params)
|
|
109
118
|
else
|
|
110
119
|
reply_error(id, METHOD_NOT_FOUND, "method not found: #{method}")
|
|
111
120
|
end
|
|
@@ -131,12 +140,28 @@ module Kapusta
|
|
|
131
140
|
{
|
|
132
141
|
capabilities: {
|
|
133
142
|
textDocumentSync: { openClose: true, change: FULL_SYNC, save: { includeText: false } },
|
|
134
|
-
documentFormattingProvider: true
|
|
143
|
+
documentFormattingProvider: true,
|
|
144
|
+
definitionProvider: true,
|
|
145
|
+
renameProvider: { prepareProvider: true }
|
|
135
146
|
},
|
|
136
147
|
serverInfo: { name: 'kapusta-ls', version: Kapusta::VERSION }
|
|
137
148
|
}
|
|
138
149
|
end
|
|
139
150
|
|
|
151
|
+
def on_initialize(params)
|
|
152
|
+
folders = params['workspaceFolders'] || []
|
|
153
|
+
roots = folders.filter_map { |f| uri_to_path(f['uri']) }
|
|
154
|
+
roots << uri_to_path(params['rootUri']) if params['rootUri']
|
|
155
|
+
roots.compact!
|
|
156
|
+
roots.uniq!
|
|
157
|
+
debug("initialize: roots=#{roots.inspect}")
|
|
158
|
+
@workspace_index = WorkspaceIndex.new(roots:)
|
|
159
|
+
@workspace_index.scan!
|
|
160
|
+
debug("workspace scan complete: #{@workspace_index.entry_count} files")
|
|
161
|
+
rescue StandardError => e
|
|
162
|
+
log("workspace scan failed: #{e.class}: #{e.message}")
|
|
163
|
+
end
|
|
164
|
+
|
|
140
165
|
def on_did_open(params)
|
|
141
166
|
doc = params['textDocument'] || {}
|
|
142
167
|
uri = doc['uri']
|
|
@@ -144,7 +169,9 @@ module Kapusta
|
|
|
144
169
|
|
|
145
170
|
version = doc['version']
|
|
146
171
|
text = doc['text'] || ''
|
|
172
|
+
debug("didOpen: uri=#{uri} version=#{version} bytes=#{text.bytesize}")
|
|
147
173
|
store(uri, text, version)
|
|
174
|
+
@workspace_index.refresh(uri, text)
|
|
148
175
|
publish_diagnostics(uri, text, version)
|
|
149
176
|
end
|
|
150
177
|
|
|
@@ -155,7 +182,9 @@ module Kapusta
|
|
|
155
182
|
return if uri.nil? || changes.empty?
|
|
156
183
|
|
|
157
184
|
text = changes.last['text']
|
|
185
|
+
debug("didChange: uri=#{uri} version=#{version} bytes=#{text.bytesize}")
|
|
158
186
|
store(uri, text, version)
|
|
187
|
+
@workspace_index.refresh(uri, text)
|
|
159
188
|
publish_diagnostics(uri, text, version)
|
|
160
189
|
end
|
|
161
190
|
|
|
@@ -164,6 +193,8 @@ module Kapusta
|
|
|
164
193
|
entry = @sources[uri]
|
|
165
194
|
return unless entry
|
|
166
195
|
|
|
196
|
+
debug("didSave: uri=#{uri}")
|
|
197
|
+
@workspace_index.refresh(uri, entry[:text])
|
|
167
198
|
publish_diagnostics(uri, entry[:text], entry[:version])
|
|
168
199
|
end
|
|
169
200
|
|
|
@@ -171,7 +202,9 @@ module Kapusta
|
|
|
171
202
|
uri = params.dig('textDocument', 'uri')
|
|
172
203
|
return unless uri
|
|
173
204
|
|
|
205
|
+
debug("didClose: uri=#{uri}")
|
|
174
206
|
@sources.delete(uri)
|
|
207
|
+
@workspace_index.remove(uri)
|
|
175
208
|
notify('textDocument/publishDiagnostics', { uri:, diagnostics: [] })
|
|
176
209
|
end
|
|
177
210
|
|
|
@@ -180,64 +213,79 @@ module Kapusta
|
|
|
180
213
|
entry = @sources[uri]
|
|
181
214
|
return [] unless entry
|
|
182
215
|
|
|
183
|
-
|
|
184
|
-
return [] if formatted == entry[:text]
|
|
185
|
-
|
|
186
|
-
[{ range: full_range(entry[:text]), newText: formatted }]
|
|
187
|
-
rescue Kapusta::Error
|
|
188
|
-
[]
|
|
216
|
+
Formatting.text_edits(entry[:text], uri_to_path(uri))
|
|
189
217
|
end
|
|
190
218
|
|
|
191
|
-
def
|
|
192
|
-
|
|
193
|
-
|
|
219
|
+
def definition(params)
|
|
220
|
+
uri = params.dig('textDocument', 'uri')
|
|
221
|
+
pos = params['position'] || {}
|
|
222
|
+
entry = @sources[uri]
|
|
223
|
+
return unless entry
|
|
194
224
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
notify('textDocument/publishDiagnostics', params)
|
|
225
|
+
result = Definition.find(uri, entry[:text], pos['line'] || 0, pos['character'] || 0,
|
|
226
|
+
workspace_index: @workspace_index)
|
|
227
|
+
debug("definition: uri=#{uri} pos=#{pos.inspect} result=#{result.inspect}")
|
|
228
|
+
result
|
|
200
229
|
end
|
|
201
230
|
|
|
202
|
-
def
|
|
203
|
-
|
|
204
|
-
[]
|
|
205
|
-
|
|
206
|
-
|
|
231
|
+
def prepare_rename(params)
|
|
232
|
+
uri = params.dig('textDocument', 'uri')
|
|
233
|
+
pos = params['position'] || {}
|
|
234
|
+
entry = @sources[uri]
|
|
235
|
+
unless entry
|
|
236
|
+
debug("prepareRename: no source for uri=#{uri.inspect}; tracked=#{@sources.keys.inspect}")
|
|
237
|
+
return
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
result = Rename.prepare(entry[:text], pos['line'] || 0, pos['character'] || 0)
|
|
241
|
+
debug("prepareRename: uri=#{uri} pos=#{pos.inspect} result=#{result.inspect}")
|
|
242
|
+
result
|
|
207
243
|
end
|
|
208
244
|
|
|
209
|
-
def
|
|
210
|
-
|
|
211
|
-
|
|
245
|
+
def handle_rename(id, params)
|
|
246
|
+
uri = params.dig('textDocument', 'uri')
|
|
247
|
+
pos = params['position'] || {}
|
|
248
|
+
new_name = params['newName']
|
|
249
|
+
entry = @sources[uri]
|
|
250
|
+
unless entry
|
|
251
|
+
debug("rename: no source for uri=#{uri.inspect}; tracked=#{@sources.keys.inspect}")
|
|
252
|
+
return reply(id, nil)
|
|
253
|
+
end
|
|
212
254
|
|
|
213
|
-
{
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
255
|
+
debug("rename: uri=#{uri} pos=#{pos.inspect} newName=#{new_name.inspect}")
|
|
256
|
+
result = Rename.perform(uri, entry[:text], pos['line'] || 0, pos['character'] || 0,
|
|
257
|
+
new_name, workspace_index: @workspace_index)
|
|
258
|
+
if result[:error]
|
|
259
|
+
debug("rename error: #{result[:error].inspect}")
|
|
260
|
+
reply_error(id, result[:error][:code], result[:error][:message])
|
|
261
|
+
else
|
|
262
|
+
edit = build_workspace_edit(result[:changes])
|
|
263
|
+
debug("rename ok: files=#{result[:changes].keys.length} edits=#{result[:changes].values.sum(&:length)}")
|
|
264
|
+
reply(id, edit)
|
|
265
|
+
end
|
|
222
266
|
end
|
|
223
267
|
|
|
224
|
-
def
|
|
225
|
-
|
|
226
|
-
|
|
268
|
+
def build_workspace_edit(changes_by_uri)
|
|
269
|
+
document_changes = changes_by_uri.map do |uri, edits|
|
|
270
|
+
sorted = edits.sort_by { |e| [-e[:range][:start][:line], -e[:range][:start][:character]] }
|
|
271
|
+
version = @sources.dig(uri, :version)
|
|
272
|
+
{
|
|
273
|
+
textDocument: { uri:, version: },
|
|
274
|
+
edits: sorted
|
|
275
|
+
}
|
|
276
|
+
end
|
|
277
|
+
{ documentChanges: document_changes }
|
|
278
|
+
end
|
|
227
279
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
match && match[0].length.positive? ? match[0].length : 1
|
|
280
|
+
def store(uri, text, version)
|
|
281
|
+
@sources[uri] = { text:, version: }
|
|
231
282
|
end
|
|
232
283
|
|
|
233
|
-
def
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
start: { line: 0, character: 0 },
|
|
239
|
-
end: { line: end_line, character: end_character }
|
|
240
|
-
}
|
|
284
|
+
def publish_diagnostics(uri, text, version)
|
|
285
|
+
diagnostics = Diagnostics.collect(text, uri_to_path(uri))
|
|
286
|
+
params = { uri:, diagnostics: }
|
|
287
|
+
params[:version] = version unless version.nil?
|
|
288
|
+
notify('textDocument/publishDiagnostics', params)
|
|
241
289
|
end
|
|
242
290
|
|
|
243
291
|
def uri_to_path(uri)
|
|
@@ -254,5 +302,11 @@ module Kapusta
|
|
|
254
302
|
def log(message)
|
|
255
303
|
@log.puts "kapusta-ls: #{message}"
|
|
256
304
|
end
|
|
305
|
+
|
|
306
|
+
def debug(message)
|
|
307
|
+
return unless @debug
|
|
308
|
+
|
|
309
|
+
@log.puts "kapusta-ls[debug]: #{message}"
|
|
310
|
+
end
|
|
257
311
|
end
|
|
258
312
|
end
|
data/lib/kapusta/version.rb
CHANGED
|
@@ -154,7 +154,23 @@ RSpec.describe 'examples-errors' do
|
|
|
154
154
|
|
|
155
155
|
it 'import-macros-missing-module.kap' do
|
|
156
156
|
expect(run_error_example('import-macros-missing-module.kap'))
|
|
157
|
-
.to eq("import-macros-missing-module.kap:4:1: import-macros
|
|
157
|
+
.to eq("import-macros-missing-module.kap:4:1: import-macros: module nonexistent-module not found\n")
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it 'import-macros-macro-not-found.kap' do
|
|
161
|
+
message = 'import-macros: macro missing not exported by module missing-macro-helper'
|
|
162
|
+
expect(run_error_example('import-macros-macro-not-found.kap'))
|
|
163
|
+
.to eq("import-macros-macro-not-found.kap:1:1: #{message}\n")
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
it 'import-macros-no-exports.kap' do
|
|
167
|
+
expect(run_error_example('import-macros-no-exports.kap'))
|
|
168
|
+
.to eq("import-macros-no-exports.kap:1:1: import-macros: module no-exports-helper has no export table\n")
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it 'import-macros-module-invalid.kap' do
|
|
172
|
+
expect(run_error_example('import-macros-module-invalid.kap'))
|
|
173
|
+
.to eq("import-macros-module-invalid.kap:1:1: import-macros expects a symbol or string module name\n")
|
|
158
174
|
end
|
|
159
175
|
|
|
160
176
|
it 'invalid-class-name.kap' do
|
data/spec/examples_spec.rb
CHANGED
|
@@ -551,4 +551,16 @@ RSpec.describe 'examples' do
|
|
|
551
551
|
50
|
|
552
552
|
OUT
|
|
553
553
|
end
|
|
554
|
+
|
|
555
|
+
it 'macros-import.kap' do
|
|
556
|
+
expect(run_example('macros-import.kap')).to eq("8\n")
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
it 'macros-import-helpers.kap' do
|
|
560
|
+
expect(run_example('macros-import-helpers.kap')).to eq("60\n")
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
it 'macros-import-whole.kap' do
|
|
564
|
+
expect(run_example('macros-import-whole.kap')).to eq("7\n")
|
|
565
|
+
end
|
|
554
566
|
end
|