Module: MilkTea::LSP::Server::ServerLifecycle

Included in:
MilkTea::LSP::Server
Defined in:
lib/milk_tea/lsp/server/lifecycle.rb

Constant Summary collapse

SEMANTIC_TOKENS_REFRESH_THROTTLE_MS =
500

Instance Method Summary collapse

Instance Method Details

#handle_cancel_request(params) ⇒ Object



154
155
156
157
158
159
160
161
162
# File 'lib/milk_tea/lsp/server/lifecycle.rb', line 154

def handle_cancel_request(params)
  request_id = params.is_a?(Hash) ? (params['id'] || params[:id]) : nil
  return nil if request_id.nil?

  @cancelled_requests_mutex.synchronize do
    @cancelled_request_ids << request_id
  end
  nil
end

#handle_did_change_configuration(params) ⇒ Object



149
150
151
152
# File 'lib/milk_tea/lsp/server/lifecycle.rb', line 149

def handle_did_change_configuration(params)
  apply_configuration_settings(params['settings'])
  nil
end

#handle_did_change_workspace_folders(params) ⇒ Object



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/milk_tea/lsp/server/lifecycle.rb', line 164

def handle_did_change_workspace_folders(params)
  event = params['event'] || {}
  added = event['added'] || []
  removed = event['removed'] || []

  removed_uris = removed.filter_map { |folder| folder.is_a?(Hash) ? folder['uri'] : nil }
  added_uris = added.filter_map { |folder| folder.is_a?(Hash) ? folder['uri'] : nil }

  if removed_uris.include?(@root_uri)
    @root_uri = added_uris.first
  elsif @root_uri.nil?
    @root_uri = added_uris.first
  end

  @workspace.workspace_root_path = uri_to_path(@root_uri)
  return nil unless @root_uri

  uris = @workspace.open_document_uris.dup
  Thread.new do
    progress = create_progress(title: 'Milk Tea LSP: Reindexing workspace', message: 'Scanning source files...')
    @workspace.index_workspace(@root_uri) { |pct, msg| progress.report(percentage: pct, message: msg) }
    doc_count = @workspace.all_documents.length
    progress.done(message: "#{doc_count} document#{doc_count == 1 ? '' : 's'} indexed")

    uris.each do |uri|
      schedule_diagnostics(uri, force: true, lint_tier: :full) unless @workspace.background_document?(uri)
    end
  end
  nil
end

#handle_exit(_params) ⇒ Object



213
214
215
216
217
# File 'lib/milk_tea/lsp/server/lifecycle.rb', line 213

def handle_exit(_params)
  stop_diagnostics_workers
  @workspace.shutdown
  exit(0)
end

#handle_initialize(params) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/milk_tea/lsp/server/lifecycle.rb', line 7

def handle_initialize(params)
  @pull_diagnostics_active = true
  if @_initialize_called
    @_indexing_thread&.join rescue nil
    @_indexing_thread = nil
    @workspace.reset
    @format_mode = :tidy
    @dependency_resolution_mode = :auto
    @platform_override = nil
    @diagnostic_report_cache.clear
    @workspace_diagnostic_cache.clear
    drain_diagnostics_queue
    @diagnostics_pending.clear
    @diagnostics_enqueued.clear
    @diagnostics_last_scheduled_hash.clear
    @diagnostics_generation.clear
    @semantic_tokens_cache.clear
    @semantic_tokens_delta_cache.clear
    @fixall_cache.clear
    @definition_file_token_cache.clear
    @definition_file_ast_cache.clear
    MilkTea::Types::Registry.reset!
  end
  @_initialize_called = true
  @root_uri = params['rootUri']
  @client_capabilities = params['capabilities'] || {}
  @workspace.workspace_root_path = uri_to_path(@root_uri)
  negotiate_position_encoding(params)
  apply_configuration_settings(params['initializationOptions'])
  apply_configuration_settings(params['settings'])
  {
    capabilities: {
      positionEncoding: @position_encoding,
      textDocumentSync: {
        openClose: true,
        change: 2,
        save: { includeText: true },
        willSaveWaitUntil: true
      },
      hoverProvider: true,
      definitionProvider: true,
      declarationProvider: true,
      typeDefinitionProvider: true,
      implementationProvider: true,
      referencesProvider: true,
      documentLinkProvider: {
        resolveProvider: true
      },
      documentHighlightProvider: true,
      documentSymbolProvider: true,
      documentFormattingProvider: true,
      documentRangeFormattingProvider: true,
      foldingRangeProvider: true,
      callHierarchyProvider: true,
      typeHierarchyProvider: true,
      selectionRangeProvider: true,
      linkedEditingRangeProvider: true,
      executeCommandProvider: {
        commands: ['mtc.restartServer']
      },
      documentOnTypeFormattingProvider: {
        firstTriggerCharacter: "\n",
        moreTriggerCharacter: []
      },
      signatureHelpProvider: {
        triggerCharacters: ['(', ','],
        retriggerCharacters: [',']
      },
      codeActionProvider: {
        codeActionKinds: ['quickFix', 'source.fixAll']
      },
      codeLensProvider: {
        resolveProvider: true
      },
      inlayHintProvider: true,
      semanticTokensProvider: {
        legend: {
          tokenTypes: SEMANTIC_TOKEN_TYPES,
          tokenModifiers: SEMANTIC_TOKEN_MODIFIERS
        },
        full: { delta: true },
        range: true
      },
      completionProvider: {
        triggerCharacters: ['.', '(', ' '],
        resolveProvider: true
      },
      renameProvider: { prepareProvider: true },
      workspaceSymbolProvider: true,
      diagnosticProvider: {
        interFileDependencies: true,
        workspaceDiagnostics: true
      },
      workspace: {
        workspaceFolders: {
          supported: true,
          changeNotifications: true,
        },
        fileOperations: {
          willRename: {
            filters: [{ pattern: { glob: '**/*.mt' }, scheme: 'file' }]
          }
        },
        didChangeWatchedFiles: {
          dynamicRegistration: true
        }
      }
    }
  }
end

#handle_initialized(_params) ⇒ Object



134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/milk_tea/lsp/server/lifecycle.rb', line 134

def handle_initialized(_params)
  pull_client_configuration if @client_capabilities&.dig('workspace', 'configuration')
  return nil unless @root_uri

  @_indexing_thread = Thread.new do
    progress = create_progress(title: 'Milk Tea LSP: Indexing workspace', message: 'Scanning source files...')
    @workspace.index_workspace(@root_uri) { |pct, msg| progress.report(percentage: pct, message: msg) }
    document_count = @workspace.all_documents.length
    progress.done(message: "#{document_count} document#{document_count == 1 ? '' : 's'} indexed")
    log_message(:info, "Milk Tea LSP ready — #{document_count} document#{document_count == 1 ? '' : 's'} indexed")
    refresh_client_semantic_tokens if document_count > 0
  end
  nil
end

#handle_shutdown(_params) ⇒ Object



207
208
209
210
211
# File 'lib/milk_tea/lsp/server/lifecycle.rb', line 207

def handle_shutdown(_params)
  stop_diagnostics_workers
  @workspace.shutdown
  nil
end

#handle_will_rename_files(params) ⇒ Object



195
196
197
198
199
200
201
202
203
204
205
# File 'lib/milk_tea/lsp/server/lifecycle.rb', line 195

def handle_will_rename_files(params)
  files = params['files'] || []
  files.each do |file|
    old_uri = file['oldUri']
    new_uri = file['newUri']
    next unless old_uri && new_uri

    @workspace.rename_indexed_file(old_uri, new_uri)
  end
  nil
end

#negotiate_position_encoding(params) ⇒ Object



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/milk_tea/lsp/server/lifecycle.rb', line 118

def negotiate_position_encoding(params)
  client_encodings = params.dig('capabilities', 'general', 'positionEncodings')
  if client_encodings.is_a?(Array)
    @position_encoding = if client_encodings.include?('utf-8')
                          'utf-8'
                        elsif client_encodings.include?('utf-32')
                          'utf-32'
                        else
                          'utf-16'
                        end
  else
    @position_encoding = 'utf-16'
  end
  Diagnostics.position_encoding = @position_encoding
end

#refresh_client_semantic_tokensObject



221
222
223
224
225
226
227
228
# File 'lib/milk_tea/lsp/server/lifecycle.rb', line 221

def refresh_client_semantic_tokens
  now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  @semantic_tokens_last_refresh ||= 0.0
  return if (now - @semantic_tokens_last_refresh) * 1000 < SEMANTIC_TOKENS_REFRESH_THROTTLE_MS

  @semantic_tokens_last_refresh = now
  @protocol.write_notification('workspace/semanticTokens/refresh', nil)
end