howl.lsp.lsp.Client

local process, client

before_each ->
  process = FakeProcess!
  client = Client cmd: 'fake-server', root: File('/tmp/proj'), :process

initialize = (capabilities = { positionEncoding: 'utf-8' }) ->
  client\start!
  init = process\messages![1]
  process\emit { jsonrpc: '2.0', id: init.id, result: { :capabilities } }

start()

sends an initialize request with the root and utf-8 as position encoding

client\start!
msg = process\messages![1]
assert.equals 'initialize', msg.method
assert.equals 'file:///tmp/proj', msg.params.rootUri
assert.same { 'utf-8' }, msg.params.capabilities.general.positionEncodings

sends initialized and stores the server capabilities once the server responds

initialize { positionEncoding: 'utf-8', completionProvider: {} }
assert.is_true client.initialized
assert.same {}, client.capabilities.completionProvider
assert.equals 'initialized', process\last_message!.method

holds back other messages until initialization is complete

client\start!
client\notify 'textDocument/didOpen', {}
assert.equals 1, #process\messages!
init = process\messages![1]
process\emit { jsonrpc: '2.0', id: init.id, result: { capabilities: { positionEncoding: 'utf-8' } } }
methods = [m.method for m in *process\messages!]
assert.same { 'initialize', 'initialized', 'textDocument/didOpen' }, methods

shuts down the server if it does not support the utf-8 position encoding

initialize {}
assert.is_false client.initialized
assert.is_true client.dead
assert.same { 'TERM' }, process.signals
assert.match client.failure, 'utf%-8'

last_used

before_each ->
  initialize!
  client.last_used = 0

is updated when sending a message

client\notify 'foo', {}
assert.is_true client.last_used > 0

is not updated when receiving a message

process\emit { jsonrpc: '2.0', method: 'foo', params: {} }
assert.equals 0, client.last_used

stop()

before_each -> initialize!

sends shutdown followed by exit

client\stop!
shutdown = process\last_message!
assert.equals 'shutdown', shutdown.method
process\emit { jsonrpc: '2.0', id: shutdown.id, result: json_rpc.null }
assert.equals 'exit', process\last_message!.method

is invoked when the process is stopped

process.stop_handler!
assert.equals 'shutdown', process\last_message!.method

does not warn when the server then exits

warn = spy.on log, 'warn'
client\stop!
process\exit!
log.warn\revert!
assert.spy(warn).was_not_called!
assert.is_nil client.failure

send_request(method, params, callback)

before_each -> initialize!

invokes callback with the result of the matching response

callback = spy.new ->
id = client\send_request 'foo', { x: 1 }, callback
msg = process\last_message!
assert.same { jsonrpc: '2.0', id: id, method: 'foo', params: { x: 1 } }, msg
process\emit { jsonrpc: '2.0', id: id + 100, result: 'other' }
assert.spy(callback).was_not_called!
process\emit { jsonrpc: '2.0', id: id, result: 'ok' }
assert.spy(callback).was_called_with 'ok'

invokes callback with nil and the message for error responses

callback = spy.new ->
id = client\send_request 'foo', {}, callback
process\emit { jsonrpc: '2.0', id: id, error: { code: 1, message: 'bad' } }
assert.spy(callback).was_called_with nil, 'bad'

invokes callback with an error when the request times out

callback = spy.new ->
client\send_request 'foo', {}, callback, 0
howl.app\pump_mainloop!
assert.spy(callback).was_called_with nil, "request 'foo' timed out"

fails pending requests when the server exits

callback = spy.new ->
client\send_request 'foo', {}, callback
process\exit!
assert.spy(callback).was_called_with nil, 'server is not running'
assert.is_true client.dead

returns nil and an error when the server is not running

process\exit!
assert.same { nil, 'server is not running' }, { client\send_request 'foo', {}, -> }

request(method, params)

waits for and returns the result

initialize!
local result
dispatch.launch -> result = client\request 'foo', {}
process\emit { jsonrpc: '2.0', id: process\last_message!.id, result: 'ok' }
assert.equals 'ok', result

cancel(id)

sends $/cancelRequest and ignores any later response

initialize!
callback = spy.new ->
id = client\send_request 'foo', {}, callback
client\cancel id
assert.same { method: '$/cancelRequest', params: { :id }, jsonrpc: '2.0' }, process\last_message!
process\emit { jsonrpc: '2.0', :id, result: 'late' }
assert.spy(callback).was_not_called!

server messages

before_each -> initialize!

invokes the registered notification handler with the params

handler = spy.new ->
client.notification_handlers['window/logMessage'] = handler
process\emit { jsonrpc: '2.0', method: 'window/logMessage', params: { message: 'hi' } }
assert.spy(handler).was_called_with { message: 'hi' }

responds to workspace/configuration requests with nulls

process\emit { jsonrpc: '2.0', id: 'x', method: 'workspace/configuration', params: { items: { {}, {} } } }
assert.includes process.written[#process.written], '"result":[null,null]'

responds with an error to unknown requests

process\emit { jsonrpc: '2.0', id: 7, method: 'foo/bar' }
msg = process\last_message!
assert.equals 7, msg.id
assert.equals -32601, msg.error.code