Code: Select all
#Include <Socket>
#Include <JSON>
Persistent
class LSPClient extends Socket.Client {
/**
* @return {LSPClient}
*/
static Call(path?, port := 1219) {
server := Socket.Server(port)
path := path ?? "C:\Users\" A_UserName "\.vscode\extensions\thqby.vscode-autohotkey2-lsp-1.8.5\server\dist\server.js"
if !FileExist(path)
throw Error()
Run('node "' path '" --socket=' port, , 'hide')
client := server.AcceptAsClient(this)
client._response := Map()
client._recvbuf := 0
client._id := 0
return client
}
; https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/
sendRequest(method, params?, callback?) {
id := ++this._id & 0xffffffff
str := JSON.stringify({
jsonrpc: '2.0',
id: id,
method: method,
params: params?
})
str := 'Content-Length:' (StrPut(str, 'utf-8') - 1) '`r`n`r`n' str
this._response[id] := callback ?? waitMsg, msg := 0
this.SendText(str)
if !IsSet(callback) {
while !msg
Sleep(10)
return msg
}
waitMsg(m) => msg := m
}
sendNotification(method, params?) {
str := JSON.stringify({
jsonrpc: '2.0',
method: method,
params: params?
})
str := 'Content-Length:' (StrPut(str, 'utf-8') - 1) '`r`n`r`n' str
this.SendText(str)
}
onClose(err) => ExitApp()
onRead(err) {
if err
goto onerr
if !buf := this._recvbuf {
if this._recv(buf := Buffer(40, 0), 40, 2) > 0 && i := InStr(s := StrGet(buf, 'utf-8'), '`n')
if RegExMatch(s, '^Content-Length:\s*(\d+)`r`n', &m)
buf := this._recvbuf := Buffer(m.Len + 2 + m[1]), buf.pos := 0, buf.skip := m.Len + 2
else {
OutputDebug('unknown line: ' (s := SubStr(s, 1, i)))
this._recv(buf := Buffer(StrPut(s, 'utf-8') - 1), buf.Size)
}
return
}
s := this._recv(buf.Ptr + buf.pos, buf.Size - buf.pos)
if s >= 0 {
if (buf.pos += s) = buf.Size {
this._recvbuf := 0
msg := StrGet(buf.Ptr + buf.skip, buf.Size - buf.skip, 'utf-8')
m := JSON.parse(msg)
try this._response.Delete(id := m.Get('id', ''))(m)
}
return
} else
err := Socket.GetLastError()
onerr: ; TODO
; throw OSError(err)
}
}
client := LSPClient()
a := client.sendRequest('initialize', {
processId: ProcessExist(),
clientInfo: {
name: 'ahk client',
version: '1.0.0'
},
initializationOptions: {
AutoLibInclude: "Disabled",
CommentTags: "^;;\s*(?<tag>.+)",
CompleteFunctionParens: false,
Diagnostics: {
ClassStaticMemberCheck: true,
ParamsCheck: true
},
ActionWhenV1IsDetected: "Continue",
FormatOptions: {
break_chained_methods: false,
ignore_comment: false,
indent_string: "`t",
keep_array_indentation: true,
max_preserve_newlines: 2,
one_true_brace: "1",
preserve_newlines: true,
space_before_conditional: true,
space_in_empty_paren: false,
space_in_other: true,
space_in_paren: false,
wrap_line_length: 0
},
InterpreterPath: "C:/Program Files/AutoHotkey/v2/AutoHotkey.exe",
WorkingDirs: [],
SymbolFoldingFromOpenBrace: false
},
capabilities: {
textDocument: {
synchronization: {
dynamicRegistration: true,
willSave: true,
willSaveWaitUntil: true,
didSave: true
},
formatting: { dynamicRegistration: true },
rangeFormatting: { dynamicRegistration: true },
onTypeFormatting: { dynamicRegistration: true } }
}
})
client.sendNotification('initialized')
client.sendNotification('textDocument/didOpen', {
textDocument: {
uri: 'ahkres:untitled',
languageId: 'ahk2',
version: 0,
text: FileRead(A_ScriptFullPath)
}
})
a := client.sendRequest('textDocument/formatting', {
textDocument: {
uri: 'ahkres:untitled'
},
options: {
tabSize: 4,
insertSpaces: true
}
})
client.sendNotification('textDocument/didClose', {
textDocument: {
uri: 'ahkres:untitled'
}
})
MsgBox a['result'][1]['newText']