Module: MilkTea::LSP::Workspace::WorkspaceModuleIndex

Included in:
MilkTea::LSP::Workspace
Defined in:
lib/milk_tea/lsp/workspace/module_index.rb

Overview

Persistent module-name index for import-line completion.

import_completions previously walked the entire module-root tree on every keystroke (stat'ing every directory entry). This index records, per module root, the importable module names reachable from each directory: .mt file basenames directly in a directory, plus subdirectory names that transitively contain a .mt file. It is built once per root and kept in sync with workspace/didChangeWatchedFiles events, so per-keystroke import completion becomes an in-memory hash lookup.

Instance Method Summary collapse

Instance Method Details

#apply_module_index_events(changes, skip_open: true) ⇒ Object

Apply workspace/didChangeWatchedFiles events to the index. Content changes (type 2) do not affect the name index; creates (1) and deletes (3) of .mt files rebuild their root once per event batch so a build producing many events triggers a single rescan. Open documents are source-of-truth and are ignored (skip_open: true), matching apply_watched_file_change; rename handling passes skip_open: false so the index never keeps a stale name for a renamed module.



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/milk_tea/lsp/workspace/module_index.rb', line 66

def apply_module_index_events(changes, skip_open: true)
  open_uris = @document_state_mutex.synchronize { @open_documents.keys }
  roots_to_rebuild = Set.new
  changes.each do |change|
    uri = change.is_a?(Hash) ? change['uri'] : nil
    change_type = change.is_a?(Hash) ? change['type'] : nil
    next unless uri && [1, 3].include?(change_type&.to_i)
    next if skip_open && open_uris.include?(uri)

    path = uri_to_path(uri)
    next unless path && path.end_with?('.mt')

    root = module_index_root_for_path(path)
    roots_to_rebuild << root if root
  end

  roots_to_rebuild.each { |root| build_module_index(root) }
  roots_to_rebuild.length
end

#ensure_module_index(root) ⇒ Object

Ensure root has an index entry, building it on first use. A race between two threads simply builds twice and the later write wins.



31
32
33
34
35
36
# File 'lib/milk_tea/lsp/workspace/module_index.rb', line 31

def ensure_module_index(root)
  present = @module_index_mutex.synchronize { @module_index.key?(root) }
  return if present

  build_module_index(root)
end

#module_importable_names(root, fs_dir, current_path: nil) ⇒ Object

Return importable module names for fs_dir under root, excluding the currently-open current_path file. Returns nil when root is unknown.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/milk_tea/lsp/workspace/module_index.rb', line 40

def module_importable_names(root, fs_dir, current_path: nil)
  ensure_module_index(root)

  entry = @module_index_mutex.synchronize { @module_index[root] }
  return nil unless entry

  result = {}
  files = entry[:mt_files][fs_dir]
  if files
    files.each do |mod_name, file_path|
      next if current_path && file_path == current_path

      result[mod_name] = true
    end
  end
  entry[:mt_dirs][fs_dir]&.each { |subdir| result[subdir] = true }
  result
end

#module_index_rootsObject



86
87
88
# File 'lib/milk_tea/lsp/workspace/module_index.rb', line 86

def module_index_roots
  @module_index_mutex.synchronize { @module_index.keys }
end

#refresh_module_index_for_workspaceObject

Rebuild the index for every module root reachable from the workspace root. Called after initialization and after workspace-folder changes.



22
23
24
25
26
27
# File 'lib/milk_tea/lsp/workspace/module_index.rb', line 22

def refresh_module_index_for_workspace
  root_path = @workspace_root_path
  return unless root_path

  module_roots_for_path(root_path).each { |root| ensure_module_index(root) }
end

#reset_module_indexObject



16
17
18
# File 'lib/milk_tea/lsp/workspace/module_index.rb', line 16

def reset_module_index
  @module_index_mutex.synchronize { @module_index.clear }
end