Class: Chutney::LSP::Server
- Inherits:
-
Object
- Object
- Chutney::LSP::Server
- Defined in:
- lib/chutney/lsp/server.rb
Overview
A minimalistic language server which will lint gherkin files on open and save
Constant Summary collapse
- LSP_CONST =
LanguageServer::Protocol::Constant
- LSP_IO =
LanguageServer::Protocol::Transport::Stdio
- LSP_IF =
LanguageServer::Protocol::Interface
Instance Method Summary collapse
- #diagnostic_message(file_uri, diagnostics) ⇒ Object
-
#initialize ⇒ Server
constructor
A new instance of Server.
- #process_message(message) ⇒ Object
- #run_did_change(message) ⇒ Object
- #run_initialize(message) ⇒ Object
- #run_initialized ⇒ Object
- #send_log(message, method: 'window/logMessage', error: false) ⇒ Object
- #send_message(message) ⇒ Object
- #send_notification(message, error: false) ⇒ Object
- #shutdown ⇒ Object
- #start ⇒ Object
- #to_diagnostic(offense) ⇒ Object
- #to_range(location) ⇒ Object
Constructor Details
#initialize ⇒ Server
Returns a new instance of Server.
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 |
# File 'lib/chutney/lsp/server.rb', line 12 def initialize @writer = LSP_IO::Writer.new @reader = LSP_IO::Reader.new @mutex = Mutex.new @incoming_queue = Thread::Queue.new @outgoing_queue = Thread::Queue.new @dispatcher = Thread.new do while ( = @outgoing_queue.pop) if .is_a? Result @mutex.synchronize { @writer.write(id: .id, result: .response) } else @mutex.synchronize { @writer.write(.to_hash) } end end end @worker = Thread.new do while ( = @incoming_queue.pop) () end end Thread.main.priority = 1 end |
Instance Method Details
#diagnostic_message(file_uri, diagnostics) ⇒ Object
119 120 121 122 123 124 125 126 127 |
# File 'lib/chutney/lsp/server.rb', line 119 def (file_uri, diagnostics) { method: 'textDocument/publishDiagnostics', params: { uri: file_uri, diagnostics: } } end |
#process_message(message) ⇒ Object
38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/chutney/lsp/server.rb', line 38 def () case [:method] when 'initialize' run_initialize() when 'initialized' run_initialized when 'textDocument/didOpen', 'textDocument/didSave' run_did_change() when 'textDocument/didClose' # no-op end end |
#run_did_change(message) ⇒ Object
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/chutney/lsp/server.rb', line 99 def run_did_change() document = .dig(:params, :textDocument) filename = document[:uri].delete_prefix('file://') send_log("Evaluating #{filename}") linter = Chutney::ChutneyLint.new(*filename) linter.configuration.quiet! begin offenses = linter.analyse.values.first.filter { |r| r[:issues].any? } rescue StandardError => e send_log("Could not parse #{filename} as Gherkin. Received: #{e.}", error: true) send_notification("Could not parse #{filename} as Gherkin.", error: true) return end send_log("Found #{offenses.count} offenses") diagnostics = offenses .flat_map { |group| group[:issues].each { |issue| issue[:linter] = group[:linter] } } .map { |offense| to_diagnostic(offense) } ((document[:uri], diagnostics)) end |
#run_initialize(message) ⇒ Object
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/chutney/lsp/server.rb', line 74 def run_initialize() initialize_result = LSP_IF::InitializeResult.new( capabilities: LSP_IF::ServerCapabilities.new( document_formatting_provider: true, text_document_sync: LSP_IF::TextDocumentSyncOptions.new( change: LSP_CONST::TextDocumentSyncKind::FULL, open_close: true, save: true ) ), server_info: { name: 'chutney-lsp', version: VERSION } ) (Result.new(id: [:id], response: initialize_result)) send_log('Initializing') end |
#run_initialized ⇒ Object
93 94 95 96 97 |
# File 'lib/chutney/lsp/server.rb', line 93 def run_initialized RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable) send_notification('Chutney LSP Server up and running') send_log('Initialized') end |
#send_log(message, method: 'window/logMessage', error: false) ⇒ Object
57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/chutney/lsp/server.rb', line 57 def send_log(, method: 'window/logMessage', error: false) type = error ? LSP_CONST::MessageType::ERROR : LSP_CONST::MessageType::INFO notification = LSP_IF::NotificationMessage.new( method:, jsonrpc: '2.0', params: LSP_IF::ShowMessageParams.new( type:, message: "Chutney LSP [#{VERSION}]: #{}" ) ) (notification) end |
#send_message(message) ⇒ Object
51 52 53 54 55 |
# File 'lib/chutney/lsp/server.rb', line 51 def () return if outgoing_queue.closed? outgoing_queue << end |
#send_notification(message, error: false) ⇒ Object
70 71 72 |
# File 'lib/chutney/lsp/server.rb', line 70 def send_notification(, error: false) send_log(, method: 'window/showMessage', error:) end |
#shutdown ⇒ Object
145 146 147 148 149 150 151 152 153 |
# File 'lib/chutney/lsp/server.rb', line 145 def shutdown incoming_queue.clear outgoing_queue.clear incoming_queue.close outgoing_queue.close worker.join dispatcher.join send_log('Shutdown complete') end |
#start ⇒ Object
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/chutney/lsp/server.rb', line 155 def start reader.read do || method = [:method] send_log("Received #{method}") case method when 'initialize', 'initialized', 'textDocument/didOpen', 'textDocument/didClose', 'textDocument/didSave' incoming_queue.push() when 'shutdown' shutdown when 'exit' mutex.synchronize do status = incoming_queue.closed? ? 0 : 1 exit(status) end end end end |
#to_diagnostic(offense) ⇒ Object
129 130 131 132 133 134 |
# File 'lib/chutney/lsp/server.rb', line 129 def to_diagnostic(offense) code = offense[:linter] = offense[:message] source = 'chutney' { code:, message:, source:, severity: 1, range: to_range(offense[:location]) } end |
#to_range(location) ⇒ Object
136 137 138 139 140 141 142 143 |
# File 'lib/chutney/lsp/server.rb', line 136 def to_range(location) return { start: { character: 0, line: 0 }, end: { character: 0, line: 0 } } unless location { start: { character: location.fetch(:column, 1) - 1, line: location.fetch(:line, 1) - 1 }, end: { character: 0, line: location.fetch(:line, 1) } } end |