mt-lang 0.3.38 → 0.3.39

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.
@@ -12,8 +12,10 @@ module MilkTea
12
12
 
13
13
  content = @workspace.get_content(uri)
14
14
  cache_key = content.hash
15
+ facts = @workspace.peek_facts(uri)
16
+ facts_id = facts&.object_id
15
17
  cached = @semantic_tokens_cache[uri]
16
- if cached && cached[:content_hash] == cache_key
18
+ if cached && cached[:content_hash] == cache_key && cached[:facts_id] == facts_id
17
19
  elapsed = elapsed_ms(total_start)
18
20
  short_uri = shorten_uri(uri) || uri
19
21
  log_perf_breakdown('textDocument/semanticTokens/full', elapsed,
@@ -25,10 +27,6 @@ module MilkTea
25
27
  tokens = @workspace.get_tokens(uri) || []
26
28
  tokens_ms = elapsed_ms(tokens_start)
27
29
 
28
- facts_start = monotonic_time
29
- facts = @workspace.get_facts(uri, allow_last_good_fallback: semantic_tokens_allow_last_good_fallback?(uri))
30
- facts_ms = elapsed_ms(facts_start)
31
-
32
30
  build_start = monotonic_time
33
31
  semantic_entries = build_semantic_token_entries(tokens, facts)
34
32
  build_ms = elapsed_ms(build_start)
@@ -38,17 +36,18 @@ module MilkTea
38
36
  encode_ms = elapsed_ms(encode_start)
39
37
 
40
38
  result_id = next_semantic_token_result_id(uri)
41
- @semantic_tokens_cache[uri] = { content_hash: cache_key, data: data, result_id: result_id }
39
+ @semantic_tokens_cache[uri] = { content_hash: cache_key, data: data, result_id: result_id, facts_id: facts_id }
42
40
  @semantic_tokens_delta_cache[uri] = {
43
41
  result_id: result_id,
44
42
  content_hash: cache_key,
43
+ facts_id: facts_id,
45
44
  entries: semantic_entries,
46
45
  }
47
46
 
48
47
  elapsed = elapsed_ms(total_start)
49
48
  short_uri = shorten_uri(uri) || uri
50
49
  log_perf_breakdown('textDocument/semanticTokens/full', elapsed,
51
- "uri=#{short_uri} bytes=#{content.bytesize} lines=#{content.count("\n") + 1} cache=miss tokens=#{tokens.length} entries=#{semantic_entries.length} data_len=#{data.length} facts=on stages_ms=tokens:#{tokens_ms},facts:#{facts_ms},build:#{build_ms},encode:#{encode_ms}")
50
+ "uri=#{short_uri} bytes=#{content.bytesize} lines=#{content.count("\n") + 1} cache=miss tokens=#{tokens.length} entries=#{semantic_entries.length} data_len=#{data.length} facts=#{facts_id ? 'on' : 'off'} stages_ms=tokens:#{tokens_ms},build:#{build_ms},encode:#{encode_ms}")
52
51
 
53
52
  { resultId: result_id, data: data }
54
53
  rescue StandardError => e
@@ -68,18 +67,20 @@ module MilkTea
68
67
 
69
68
  content = @workspace.get_content(uri)
70
69
  cache_key = content.hash
71
- if cached[:content_hash] == cache_key
70
+ facts = @workspace.peek_facts(uri)
71
+ facts_id = facts&.object_id
72
+ if cached[:content_hash] == cache_key && cached[:facts_id] == facts_id
72
73
  return { resultId: cached[:result_id], edits: [] }
73
74
  end
74
75
 
75
76
  tokens = @workspace.get_tokens(uri) || []
76
- facts = @workspace.get_facts(uri, allow_last_good_fallback: semantic_tokens_allow_last_good_fallback?(uri))
77
77
  new_entries = build_semantic_token_entries(tokens, facts)
78
78
 
79
79
  new_result_id = next_semantic_token_result_id(uri)
80
80
  @semantic_tokens_delta_cache[uri] = {
81
81
  result_id: new_result_id,
82
82
  content_hash: cache_key,
83
+ facts_id: facts_id,
83
84
  entries: new_entries,
84
85
  }
85
86
 
@@ -109,7 +110,7 @@ module MilkTea
109
110
  ![:newline, :indent, :dedent, :eof].include?(t.type)
110
111
  end
111
112
 
112
- facts = @workspace.get_facts(uri, allow_last_good_fallback: semantic_tokens_allow_last_good_fallback?(uri))
113
+ facts = @workspace.peek_facts(uri)
113
114
  semantic_entries = build_semantic_token_entries(range_tokens, facts)
114
115
 
115
116
  entries_in_range = semantic_entries.select do |entry|
@@ -128,6 +129,7 @@ module MilkTea
128
129
  # is O(1) per call instead of O(n), turning the overall build from O(n²) to O(n).
129
130
  trivia_types = Set[:newline, :indent, :dedent, :eof]
130
131
  @tokens_by_line_cache = Hash.new { |h, k| h[k] = [] }
132
+ @semantic_token_build_cache = {}
131
133
  tokens.each { |t| @tokens_by_line_cache[t.line] << t unless trivia_types.include?(t.type) }
132
134
  @attribute_name_semantic_overrides = build_attribute_name_semantic_overrides(tokens, facts)
133
135
 
@@ -152,6 +154,24 @@ module MilkTea
152
154
  ensure
153
155
  @tokens_by_line_cache = nil
154
156
  @attribute_name_semantic_overrides = nil
157
+ @semantic_token_build_cache = nil
158
+ end
159
+
160
+ # Memoize facts-derived lookups for the duration of one semantic-token
161
+ # build. The build cache is scoped per build (set up and torn down in
162
+ # build_semantic_token_entries) so repeated per-token classifications do
163
+ # not re-scan frames/type scopes/types for every identifier token.
164
+ def semantic_build_memo?(key)
165
+ !@semantic_token_build_cache.nil? && @semantic_token_build_cache.key?(key)
166
+ end
167
+
168
+ def semantic_build_memo(key)
169
+ @semantic_token_build_cache[key][1] if @semantic_token_build_cache&.key?(key)
170
+ end
171
+
172
+ def store_semantic_build_memo(key, value)
173
+ @semantic_token_build_cache[key] = [:ok, value] if @semantic_token_build_cache
174
+ value
155
175
  end
156
176
 
157
177
  def fstring_interpolation_entries(fstring_tok, facts)
@@ -1052,11 +1072,23 @@ module MilkTea
1052
1072
 
1053
1073
  def known_type_name?(facts, name)
1054
1074
  return false unless facts
1055
- return true if facts.types.key?(name)
1056
- facts.types.each_value do |type|
1057
- return true if type.respond_to?(:nested_types) && type.nested_types.key?(name)
1075
+ memo_key = [:known_type, facts.object_id, name]
1076
+ return semantic_build_memo(memo_key) if semantic_build_memo?(memo_key)
1077
+
1078
+ result = if facts.types.key?(name)
1079
+ true
1080
+ else
1081
+ found = false
1082
+ facts.types.each_value do |type|
1083
+ if type.respond_to?(:nested_types) && type.nested_types.key?(name)
1084
+ found = true
1085
+ break
1086
+ end
1087
+ end
1088
+ found
1058
1089
  end
1059
- false
1090
+
1091
+ store_semantic_build_memo(memo_key, result)
1060
1092
  end
1061
1093
 
1062
1094
  def dot_nested_type_member?(tokens, index, facts)
@@ -1317,6 +1349,9 @@ module MilkTea
1317
1349
  end
1318
1350
 
1319
1351
  def type_parameter_names_in_scope(facts, line)
1352
+ memo_key = [:type_param_names, facts.object_id, line]
1353
+ return semantic_build_memo(memo_key) if semantic_build_memo?(memo_key)
1354
+
1320
1355
  scopes = @type_parameter_scope_cache ||= {}
1321
1356
  cached = scopes[facts.object_id]
1322
1357
  unless cached
@@ -1330,11 +1365,15 @@ module MilkTea
1330
1365
  scopes[facts.object_id] = cached
1331
1366
  end
1332
1367
 
1368
+ result = []
1333
1369
  cached.reverse_each do |scope|
1334
- return scope[:names] if line >= scope[:start_line] && line <= scope[:end_line]
1370
+ if line >= scope[:start_line] && line <= scope[:end_line]
1371
+ result = scope[:names]
1372
+ break
1373
+ end
1335
1374
  end
1336
1375
 
1337
- []
1376
+ store_semantic_build_memo(memo_key, result)
1338
1377
  end
1339
1378
 
1340
1379
  def type_parameter_scopes_for_declaration(decl, end_line: Float::INFINITY)
@@ -10,7 +10,7 @@ module MilkTea
10
10
  content = params['textDocument']['text']
11
11
  source = @workspace.document_source(uri) || 'unknown'
12
12
  open_start = monotonic_time
13
- open_stats = @workspace.open_document(uri, content)
13
+ open_stats = @workspace.open_document(uri, content, warm_facts: false)
14
14
  open_ms = elapsed_ms(open_start)
15
15
  @semantic_tokens_cache.delete(uri)
16
16
  @semantic_tokens_delta_cache.delete(uri)
@@ -38,7 +38,7 @@ module MilkTea
38
38
  changes = params['contentChanges'] || []
39
39
  previous_content = @workspace.get_content(uri)
40
40
 
41
- @workspace.apply_incremental_changes(uri, changes)
41
+ @workspace.apply_incremental_changes(uri, changes, warm_facts: false)
42
42
 
43
43
  invalidate_document_caches(uri)
44
44
  current_content = @workspace.get_content(uri)
@@ -115,12 +115,12 @@ module MilkTea
115
115
  def find_type_info(facts, name)
116
116
  if (t = facts.types[name])
117
117
  ast = t.respond_to?(:ast_declaration) ? t.ast_declaration : nil
118
- return { kind: type_kind_from_object(t), line: ast&.line, column: ast.column || 0 }
118
+ return { kind: type_kind_from_object(t), line: ast&.line, column: ast&.column || 0 }
119
119
  end
120
120
 
121
121
  if (t = facts.interfaces[name])
122
122
  ast = t.respond_to?(:ast_declaration) ? t.ast_declaration : nil
123
- return { kind: :interface, line: ast&.line, column: ast.column || 0 }
123
+ return { kind: :interface, line: ast&.line, column: ast&.column || 0 }
124
124
  end
125
125
 
126
126
  nil
@@ -177,6 +177,7 @@ module MilkTea
177
177
 
178
178
  def handle_did_change_watched_files(params)
179
179
  changes = params['changes'] || []
180
+ @workspace.apply_module_index_events(changes)
180
181
  affected_uris = Set.new
181
182
  changes.each do |change|
182
183
  uri = change['uri']
@@ -198,6 +199,9 @@ module MilkTea
198
199
  @semantic_tokens_cache.delete(uri)
199
200
  @semantic_tokens_delta_cache.delete(uri)
200
201
  @fixall_cache.delete(uri)
202
+ @document_symbol_cache.delete(uri)
203
+ @completion_docs_cache.clear
204
+ @completion_resolve_cache.clear
201
205
  path = uri_to_path(uri)
202
206
  if path
203
207
  prefix = "#{path}:"
@@ -209,6 +209,15 @@ module MilkTea
209
209
  @fixall_cache = {}
210
210
  @definition_file_token_cache = {}
211
211
  @definition_file_ast_cache = {}
212
+ # Completion session re-filtering pool keyed by [uri, line].
213
+ @completion_session_cache = {}
214
+ @completion_session_order = []
215
+ @completion_session_order_set = Set.new
216
+ # Server-scoped documentation / resolve memoization keyed by [uri, name].
217
+ @completion_docs_cache = {}
218
+ @completion_resolve_cache = {}
219
+ # Enriched documentSymbol outline keyed by uri -> content_hash.
220
+ @document_symbol_cache = {}
212
221
  @diagnostics_perf = {
213
222
  scheduled: 0,
214
223
  skipped_unchanged: 0,
@@ -62,8 +62,11 @@ module MilkTea
62
62
  nil
63
63
  end
64
64
 
65
- def warm_document_facts(uri, content)
66
- ast = ast_for_content(uri, content)
65
+ # Eagerly compute and cache facts for +uri+. The keystroke path
66
+ # (didChange) passes warm: false so the sema/import-resolution cost runs
67
+ # on the debounced diagnostics worker instead of the request thread;
68
+ # requests then serve last-good facts until the background pass lands.
69
+ def warm_document_facts(uri, content, warm: true)
67
70
  stats = {
68
71
  bytes: content.bytesize,
69
72
  lines: content.count("\n") + 1,
@@ -71,7 +74,7 @@ module MilkTea
71
74
  facts_mode: nil,
72
75
  facts_ms: nil,
73
76
  skip_reason: nil,
74
- import_count: ast.respond_to?(:imports) ? ast.imports.length : 0,
77
+ import_count: 0,
75
78
  shared_module_cache_size: @shared_module_cache.length,
76
79
  }
77
80
 
@@ -80,6 +83,14 @@ module MilkTea
80
83
  return stats
81
84
  end
82
85
 
86
+ unless warm
87
+ stats[:skip_reason] = :deferred
88
+ return stats
89
+ end
90
+
91
+ ast = ast_for_content(uri, content)
92
+ stats[:import_count] = ast.respond_to?(:imports) ? ast.imports.length : 0
93
+
83
94
  facts_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
84
95
  facts_path = uri_to_path(uri)
85
96
  get_facts(uri)
@@ -23,7 +23,12 @@ module MilkTea
23
23
  end
24
24
 
25
25
  def get_ast(uri)
26
- @ast_cache[uri] ||= parse_document(uri)
26
+ @ast_cache[uri] ||= begin
27
+ # Prefer the facts' own AST so consumers that iterate the AST and
28
+ # resolve bindings via facts.binding_resolution operate on the same
29
+ # node instances (binding IDs are keyed by AST node object_id).
30
+ peek_facts(uri)&.ast || parse_document(uri)
31
+ end
27
32
  end
28
33
 
29
34
  def get_facts(uri, allow_last_good_fallback: true)
@@ -31,6 +36,17 @@ module MilkTea
31
36
  snapshot&.facts
32
37
  end
33
38
 
39
+ # Return the most recent facts already computed for +uri+ without ever
40
+ # running analysis or blocking on the analysis mutex. Used by hot request
41
+ # paths (semantic tokens) that must not stall the request thread on sema;
42
+ # they serve the latest cached/last-good facts and are told to re-fetch
43
+ # once fresh facts land in the background.
44
+ def peek_facts(uri)
45
+ @facts_cache_mutex.synchronize do
46
+ @facts_cache[uri] || @last_good_facts_cache[uri]
47
+ end
48
+ end
49
+
34
50
  def get_tooling_snapshot(uri, allow_last_good_fallback: true)
35
51
  total_start = perf_logging? ? monotonic_time : nil
36
52
  cache_state = 'miss'
@@ -67,6 +67,10 @@ module MilkTea
67
67
  @facts_cache[uri] = facts if facts
68
68
  @last_good_facts_cache[uri] = facts if facts
69
69
  @document_module_names[uri] = facts.module_name if facts&.module_name
70
+ # Consumers resolve binding_resolution against get_ast(uri) node
71
+ # object_ids; keep the AST cache aligned with the facts that
72
+ # reference it so binding lookups line up across the worker path.
73
+ @ast_cache[uri] = snapshot.facts.ast if snapshot&.facts&.ast
70
74
  update_dependency_index(uri, facts)
71
75
  @diagnostics_cache[uri] = {
72
76
  content_hash: hash,
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module LSP
5
+ class Workspace
6
+ # Persistent module-name index for import-line completion.
7
+ #
8
+ # `import_completions` previously walked the entire module-root tree on
9
+ # every keystroke (stat'ing every directory entry). This index records, per
10
+ # module root, the importable module names reachable from each directory:
11
+ # `.mt` file basenames directly in a directory, plus subdirectory names that
12
+ # transitively contain a `.mt` file. It is built once per root and kept in
13
+ # sync with workspace/didChangeWatchedFiles events, so per-keystroke import
14
+ # completion becomes an in-memory hash lookup.
15
+ module WorkspaceModuleIndex
16
+ def reset_module_index
17
+ @module_index_mutex.synchronize { @module_index.clear }
18
+ end
19
+
20
+ # Rebuild the index for every module root reachable from the workspace
21
+ # root. Called after initialization and after workspace-folder changes.
22
+ def refresh_module_index_for_workspace
23
+ root_path = @workspace_root_path
24
+ return unless root_path
25
+
26
+ module_roots_for_path(root_path).each { |root| ensure_module_index(root) }
27
+ end
28
+
29
+ # Ensure +root+ has an index entry, building it on first use. A race
30
+ # between two threads simply builds twice and the later write wins.
31
+ def ensure_module_index(root)
32
+ present = @module_index_mutex.synchronize { @module_index.key?(root) }
33
+ return if present
34
+
35
+ build_module_index(root)
36
+ end
37
+
38
+ # Return importable module names for +fs_dir+ under +root+, excluding the
39
+ # currently-open +current_path+ file. Returns nil when +root+ is unknown.
40
+ def module_importable_names(root, fs_dir, current_path: nil)
41
+ ensure_module_index(root)
42
+
43
+ entry = @module_index_mutex.synchronize { @module_index[root] }
44
+ return nil unless entry
45
+
46
+ result = {}
47
+ files = entry[:mt_files][fs_dir]
48
+ if files
49
+ files.each do |mod_name, file_path|
50
+ next if current_path && file_path == current_path
51
+
52
+ result[mod_name] = true
53
+ end
54
+ end
55
+ entry[:mt_dirs][fs_dir]&.each { |subdir| result[subdir] = true }
56
+ result
57
+ end
58
+
59
+ # Apply workspace/didChangeWatchedFiles events to the index. Content
60
+ # changes (type 2) do not affect the name index; creates (1) and deletes
61
+ # (3) of `.mt` files rebuild their root once per event batch so a build
62
+ # producing many events triggers a single rescan. Open documents are
63
+ # source-of-truth and are ignored (skip_open: true), matching
64
+ # apply_watched_file_change; rename handling passes skip_open: false so
65
+ # the index never keeps a stale name for a renamed module.
66
+ def apply_module_index_events(changes, skip_open: true)
67
+ open_uris = @document_state_mutex.synchronize { @open_documents.keys }
68
+ roots_to_rebuild = Set.new
69
+ changes.each do |change|
70
+ uri = change.is_a?(Hash) ? change['uri'] : nil
71
+ change_type = change.is_a?(Hash) ? change['type'] : nil
72
+ next unless uri && [1, 3].include?(change_type&.to_i)
73
+ next if skip_open && open_uris.include?(uri)
74
+
75
+ path = uri_to_path(uri)
76
+ next unless path && path.end_with?('.mt')
77
+
78
+ root = module_index_root_for_path(path)
79
+ roots_to_rebuild << root if root
80
+ end
81
+
82
+ roots_to_rebuild.each { |root| build_module_index(root) }
83
+ roots_to_rebuild.length
84
+ end
85
+
86
+ def module_index_roots
87
+ @module_index_mutex.synchronize { @module_index.keys }
88
+ end
89
+
90
+ private
91
+
92
+ def module_index_root_for_path(path)
93
+ expanded = File.expand_path(path)
94
+ @module_index_mutex.synchronize do
95
+ @module_index.each_key do |root|
96
+ return root if expanded == root || expanded.start_with?(root + File::SEPARATOR)
97
+ end
98
+ end
99
+ nil
100
+ end
101
+
102
+ # Scan +root+ once and record, for every directory, the importable module
103
+ # names: `.mt` file basenames in that directory and subdirectory names
104
+ # that transitively contain a `.mt` file.
105
+ def build_module_index(root)
106
+ mt_files = Hash.new { |hash, key| hash[key] = {} }
107
+ mt_dirs = Hash.new { |hash, key| hash[key] = Set.new }
108
+
109
+ files = Dir.glob(File.join(root, '**', '*.mt')).sort
110
+ prefix = root + File::SEPARATOR
111
+ files.each do |file_path|
112
+ rel = file_path.delete_prefix(prefix)
113
+ dir = File.dirname(rel)
114
+ dir = '' if dir == '.'
115
+ mod_name = File.basename(rel, '.mt')
116
+
117
+ mt_files[dir][mod_name] = File.expand_path(file_path)
118
+
119
+ acc = ''
120
+ dir.split(File::SEPARATOR).each do |segment|
121
+ mt_dirs[acc] << segment
122
+ acc = acc.empty? ? segment : "#{acc}#{File::SEPARATOR}#{segment}"
123
+ end
124
+ end
125
+
126
+ @module_index_mutex.synchronize do
127
+ @module_index[root] = { mt_files: mt_files, mt_dirs: mt_dirs }
128
+ end
129
+ nil
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -29,13 +29,13 @@ module MilkTea
29
29
 
30
30
  # ── Document lifecycle ──────────────────────────────────────────────────
31
31
 
32
- def open_document(uri, content)
32
+ def open_document(uri, content, warm_facts: true)
33
33
  @document_state_mutex.synchronize do
34
34
  @open_documents[uri] = content
35
35
  end
36
36
  invalidate_cache(uri)
37
37
  enqueue_definition_warmup(uri) unless background_document?(uri)
38
- warm_document_facts(uri, content)
38
+ warm_document_facts(uri, content, warm: warm_facts)
39
39
  end
40
40
 
41
41
  def close_document(uri)
@@ -56,7 +56,7 @@ module MilkTea
56
56
  enqueue_definition_warmup(uri) unless background_document?(uri)
57
57
  end
58
58
 
59
- def apply_incremental_changes(uri, changes)
59
+ def apply_incremental_changes(uri, changes, warm_facts: true)
60
60
  content = get_content(uri)
61
61
  edits = Array(changes)
62
62
 
@@ -79,7 +79,7 @@ module MilkTea
79
79
  end
80
80
  dependent_uris.each { |dep_uri| invalidate_cache(dep_uri) }
81
81
 
82
- warm_document_facts(uri, new_content)
82
+ warm_document_facts(uri, new_content, warm: warm_facts)
83
83
  end
84
84
 
85
85
  def apply_incremental_change_to_content(content, change)
@@ -107,11 +107,16 @@ module MilkTea
107
107
  end
108
108
 
109
109
  # Index all .mt files under root_uri so they are available for workspace-wide queries.
110
+ # Skips the per-file sweep when the on-disk file set is unchanged since the last
111
+ # index, so repeated workspace/symbol calls do not re-walk and re-read the tree.
110
112
  def index_workspace(root_uri, &progress)
111
113
  root_path = uri_to_path(root_uri)
112
114
  return unless root_path && File.directory?(root_path)
113
115
 
114
116
  paths = Dir.glob(File.join(root_path, '**', '*.mt')).sort
117
+ return if @indexed_workspace_paths == paths
118
+
119
+ @indexed_workspace_paths = paths
115
120
  total = paths.length
116
121
  paths.each_with_index do |path, idx|
117
122
  file_uri = path_to_uri(path)
@@ -11,6 +11,7 @@ require_relative 'workspace/analysis'
11
11
  require_relative 'workspace/dependency_graph'
12
12
  require_relative 'workspace/definition_index'
13
13
  require_relative 'workspace/collection'
14
+ require_relative 'workspace/module_index'
14
15
  require_relative 'workspace/utilities'
15
16
 
16
17
  module MilkTea
@@ -81,6 +82,10 @@ module MilkTea
81
82
  @identifier_index = {}
82
83
  @identifier_index_mutex = Mutex.new
83
84
  @indexed_uris = Set.new
85
+ # Module index: root path -> { mt_files: {dir => {name => path}}, mt_dirs: {dir => Set<subdir>} }
86
+ @module_index = {}
87
+ @module_index_mutex = Mutex.new
88
+ @indexed_workspace_paths = nil
84
89
  end
85
90
 
86
91
  def reset
@@ -113,6 +118,8 @@ module MilkTea
113
118
  @reverse_import_dependents.clear
114
119
  @diagnostics_cache.clear
115
120
  @facts_generation.clear
121
+ @module_index_mutex.synchronize { @module_index.clear }
122
+ @indexed_workspace_paths = nil
116
123
  end
117
124
 
118
125
  include WorkspaceStore
@@ -121,6 +128,7 @@ module MilkTea
121
128
  include WorkspaceDependencyGraph
122
129
  include WorkspaceDefinitionIndex
123
130
  include WorkspaceCollection
131
+ include WorkspaceModuleIndex
124
132
  include WorkspaceUtilities
125
133
  end
126
134
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mt-lang
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.38
4
+ version: 0.3.39
5
5
  platform: ruby
6
6
  authors:
7
7
  - Long (Teefan) Tran
@@ -149,6 +149,7 @@ files:
149
149
  - docs/index.html
150
150
  - docs/language-design.md
151
151
  - docs/language-manual.md
152
+ - docs/lsp-performance.md
152
153
  - lib/milk_tea.rb
153
154
  - lib/milk_tea/base.rb
154
155
  - lib/milk_tea/bindings.rb
@@ -338,6 +339,7 @@ files:
338
339
  - lib/milk_tea/lsp/workspace/collection.rb
339
340
  - lib/milk_tea/lsp/workspace/definition_index.rb
340
341
  - lib/milk_tea/lsp/workspace/dependency_graph.rb
342
+ - lib/milk_tea/lsp/workspace/module_index.rb
341
343
  - lib/milk_tea/lsp/workspace/store.rb
342
344
  - lib/milk_tea/lsp/workspace/utilities.rb
343
345
  - lib/milk_tea/packages.rb
@@ -624,7 +626,7 @@ metadata:
624
626
  homepage_uri: https://teefan.github.io/mt-lang/
625
627
  source_code_uri: https://github.com/teefan/mt-lang
626
628
  post_install_message: |
627
- Milk Tea 0.3.38 installed!
629
+ Milk Tea 0.3.39 installed!
628
630
 
629
631
  System requirements:
630
632
  - A C compiler (gcc or clang) must be available on PATH