Issues with AutoHotkey v2 LSP + Sublime Text 4, etc.

Discuss features, issues, about Editors for AHK
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Issues with AutoHotkey v2 LSP + Sublime Text 4, etc.

Post by lexikos » 18 Dec 2022, 20:06

thqby wrote:
22 Oct 2022, 04:52
https://github.com/thqby/vscode-autohotkey2-lsp#use-in-other-editors
ahk2 in vim, neovim and sublime text4
I thought I would test these, but I think vim is the sort of unique tool that one can't simply download and start using without any prior experience or reading. I don't have any interest in learning how to operate it myself, so recommending it to a beginner is out of the question.

I installed Sublime Text 4 to try with vscode-autohotkey2-lsp. The instructions could use clarification (I assume it would be much better if the LSP was available as an installable package).
  • "Update the PATH of nodejs and lsp-ahk2" means replace ahk2-lsp folder with the folder where the LSP was downloaded. It would help to add < ... > or % ... %, use upper-case, or do anything else to show that this is a placeholder. Perhaps literally write "replace xxx with the path to vscode-autohotkey2-lsp".
  • I found no need to update the nodejs path, because node is on the PATH.
  • It's not clear whether installing nodejs would be required as a step the user must complete, if they are installing Sublime Text and LSP on a fresh system.
Unfortunately, the LSP does not work for me.
lsp-ahk2 exited with status code -32603. Do you want to restart it? If you choose Cancel, it will be disabled for this window for the duration of the current session. Re-enable by running "LSP: Enable Language Server In Project" from the Command Palette.

--- Error: ---
Request initialize failed with message: Cannot read properties of undefined (reading 'map')

User avatar
thqby
Posts: 408
Joined: 16 Apr 2021, 11:18
Contact:

Re: Issues with AutoHotkey v2 LSP + Sublime Text 4, etc.

Post by thqby » 19 Dec 2022, 01:32

lexikos wrote:
18 Dec 2022, 20:06
[*]I found no need to update the nodejs path, because node is on the PATH.
If nodejs is not installed, nodejs will be installed when the sublime text4 lsp is installed. In this case, it seems that node.exe is not on the PATH.
lexikos wrote:
18 Dec 2022, 20:06
Unfortunately, the LSP does not work for me.
This problem is likely to occur in recent releases. A parameter that should be optional is missing, "WorkingDirs": [],.

Feather
Posts: 24
Joined: 14 Nov 2022, 02:14

Re: Issues with AutoHotkey v2 LSP + Sublime Text 4, etc.

Post by Feather » 19 Dec 2022, 04:22

@thqby
I remember you saying that v2 LSP can be used in the browser as well. So would it be possible to load it with WebView2 so that we can call v2 LSP externally?
I just want SciTE to be able to call it to format the code.

User avatar
thqby
Posts: 408
Joined: 16 Apr 2021, 11:18
Contact:

Re: Issues with AutoHotkey v2 LSP + Sublime Text 4, etc.

Post by thqby » 19 Dec 2022, 07:15

Why use webview2 for external calls?
You can compile cli.js that passes arguments from the command line.

Feather
Posts: 24
Joined: 14 Nov 2022, 02:14

Re: Issues with AutoHotkey v2 LSP + Sublime Text 4, etc.

Post by Feather » 19 Dec 2022, 08:25

Sorry I don't know how to call cli.js, it's part of node? I have some knowledge of editing and debugging AutoHotkey with SciTE, but I don't know js or node.

User avatar
thqby
Posts: 408
Joined: 16 Apr 2021, 11:18
Contact:

Re: Issues with AutoHotkey v2 LSP + Sublime Text 4, etc.

Post by thqby » 19 Dec 2022, 09:09

https://github.com/thqby/vscode-autohotkey2-lsp/blob/main/server/cli/cli.ts

This is a simple command line tool that only supports formatting ahk2 code. Invoke from the command line, node.exe cli.js path="ahkv2 script.ahk", then the formatted code is output through stdout.
Attachments
cli.js
(382.1 KiB) Downloaded 180 times

Feather
Posts: 24
Joined: 14 Nov 2022, 02:14

Re: Issues with AutoHotkey v2 LSP + Sublime Text 4, etc.

Post by Feather » 19 Dec 2022, 10:23

Thanks to you, I managed to format my code.
I don't know much about js scripts, but I have used a google translate script that calls ComObject("htmlfile"), can I understand that it can also be used as a container for cli.ts? That way I can just input the string and output the string.

User avatar
thqby
Posts: 408
Joined: 16 Apr 2021, 11:18
Contact:

Re: Issues with AutoHotkey v2 LSP + Sublime Text 4, etc.

Post by thqby » 22 Dec 2022, 20:01

@Feather
Even though I compiled the plugin to es5 standard,htmlfile and chakra's jscript engine still couldn't run the script because their regular expressions didn't support poslookbehind.
It works in the Chrome/Edge console. Webview2 can also run, but executing too long a script through ExecuteScript will fail.
image.png
image.png (78.92 KiB) Viewed 3894 times

User avatar
thqby
Posts: 408
Joined: 16 Apr 2021, 11:18
Contact:

Re: Issues with AutoHotkey v2 LSP + Sublime Text 4, etc.

Post by thqby » 24 Dec 2022, 08:22

This is a simple language client implemented with ahk, which formats ahk code through vscode-autohotkey2-lsp server.
The library used is from https://github.com/thqby/ahk2_lib.

Code: Select all

#Include <child_process>
#Include <JSON>

class LanguageClient extends child_process {
_id := 0
_msgsize := 0
_revsize := 0
_revmsg := ''
_msgcbs := Map()
__New(lsp_path) {
super.__New('node "' lsp_path '" --stdio', , , { encoding: 'utf-8' })
this.onData := this._onData
}

_onData(tp, line) {
if tp == 'stderr'
return OutputDebug(line '`n')

if SubStr(line, 1, 14) = 'Content-Length'
return (this._msgsize := Integer(SubStr(line, 16)), this._revmsg := '', this._revsize := 0)

if !line || !this._msgsize
return
this._revmsg .= line
if (diff := (this._revsize += StrPut(line, 'utf-8') - 1) - this._msgsize) >= 0 {
if diff
msg := JSON.parse(s := SubStr(this._revmsg, 1, -diff)), this._onData('stdout', SubStr(this._revmsg, this._msgsize + 1))
else
msg := JSON.parse(s := this._revmsg)
try if cb := this._msgcbs.Delete(msg['id'])
cb(msg)
catch UnsetItemError
OutputDebug(s '`n')
}
}

sendMessage(method, params?, callback?) {
if !IsSet(callback)
callback := waitMsg, id := ++this._id & 0xffffffff
else if 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
(stdin := this.stdin).Write(str)
res := 0, this._msgcbs[id ?? ''] := callback, stdin.Read(0)
if callback != waitMsg
return
while !res
Sleep(10)
return res
waitMsg(msg) => (res := msg)
}
}


Persistent
path := 'vscode-autohotkey2-lsp\server\dist\server.js'
if !FileExist(path)
throw ValueError('Script does not exist')
client := LanguageClient(path)
a := client.sendMessage('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.sendMessage('initialized', , 0)
client.sendMessage('textDocument/didOpen', {
textDocument: {
uri: 'ahkres:untitled',
languageId: 'ahk2',
version: 0,
text: FileRead(A_ScriptFullPath)
}
}, 0)
a := client.sendMessage('textDocument/formatting', {
textDocument: {
uri: 'ahkres:untitled'
},
options: {
tabSize: 4,
insertSpaces: true
}
})
MsgBox a['result'][1]['newText']
ExitApp

Feather
Posts: 24
Joined: 14 Nov 2022, 02:14

Re: Issues with AutoHotkey v2 LSP + Sublime Text 4, etc.

Post by Feather » 24 Dec 2022, 09:05

@thqby Thanks a lot, this is exciting, so that even ahk edit controls can format AutoHotkey code.
I didn't run the example correctly though, and I got this error.

Code: Select all

Error: Type mismatch.

Specifically: MinParams

	---- Lib\child_process.ahk
	069: }
	069: Else
▶	069: SetTimer(this, 20, 10000)
	070: {
	071: For h in obj
It's worth noting that you can't find server.js in the vscode-autohotkey2-lsp repository, but you can find it in the vscode directory.
Of course you must also have node installed.

User avatar
thqby
Posts: 408
Joined: 16 Apr 2021, 11:18
Contact:

Re: Issues with AutoHotkey v2 LSP + Sublime Text 4, etc.

Post by thqby » 24 Dec 2022, 09:14

i run it in v2.0.0

Feather
Posts: 24
Joined: 14 Nov 2022, 02:14

Re: Issues with AutoHotkey v2 LSP + Sublime Text 4, etc.

Post by Feather » 24 Dec 2022, 10:23

I'm using v2.0.0_H, but my AutoHotkey scripts are loaded via "\\.\pipe", which may be the cause of the problem. When I save it to AutoHotkey.ahk, the example works fine.

Feather
Posts: 24
Joined: 14 Nov 2022, 02:14

Re: Issues with AutoHotkey v2 LSP + Sublime Text 4, etc.

Post by Feather » 24 Dec 2022, 10:53

@thqby
When I use 'SetTimer(ObjBindMethod(this, "call"), 20, 10000)', the pipe loads properly too. But I'm a little curious about what's causing this difference.

I want to load this script as a system service, which means it will run forever. But SetTimer() seems to cause a performance waste, can we actively read and write stdin data?

User avatar
thqby
Posts: 408
Joined: 16 Apr 2021, 11:18
Contact:

Re: Issues with AutoHotkey v2 LSP + Sublime Text 4, etc.

Post by thqby » 25 Dec 2022, 03:41

I don't have your code to determine the cause of the error. This can be solved by adding the MinParams attribute to the class.
Synchronous mode reads block the current script. Using asynchronous mode is a relatively simple implementation.
There are two problems with active reading. Asynchronous reading may miss messages from the server, resulting in buffer overflow. Synchronous reads block scripts when there is no message, or start a thread to read and send concurrent messages to the main thread for processing.
Of course, the lsp server also supports the transmission of information by socket. The socket library of ahk processes the reply by message, which can avoid the use of settimer to continuously check stdout. But in my measurements, this performance loss is negligible.

Also, use client.sendMessage('textDocument/didClose', {textDocument: {uri: 'ahkres:untitled'}}, 0) to close unused documents.

Feather
Posts: 24
Joined: 14 Nov 2022, 02:14

Re: Issues with AutoHotkey v2 LSP + Sublime Text 4, etc.

Post by Feather » 26 Dec 2022, 16:28

Thank you very much. I searched and it seems that node also supports ipc connections, which seems to mean that we can finish the call with just one function, CallNamedPipe().
I refer to this mpv post for the call method.
viewtopic.php?f=6&t=110588

User avatar
thqby
Posts: 408
Joined: 16 Apr 2021, 11:18
Contact:

Re: Issues with AutoHotkey v2 LSP + Sublime Text 4, etc.

Post by thqby » 24 Jan 2023, 10:01

@Feather @crocodile
The following is a short implementation of socket mode.

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']
Last edited by thqby on 27 Jan 2023, 01:56, edited 1 time in total.

crocodile
Posts: 98
Joined: 28 Dec 2020, 13:41

Re: Issues with AutoHotkey v2 LSP + Sublime Text 4, etc.

Post by crocodile » 25 Jan 2023, 00:49

Thank you for the example.
Did the call method change, please? I used the call method from the first example and it returned an error message.
{"jsonrpc":"2.0","id":4,"error":{"code":-32603,"message":"Request textDocument/formatting failed with message: Cannot read properties of undefined (reading 'document')"}}

User avatar
thqby
Posts: 408
Joined: 16 Apr 2021, 11:18
Contact:

Re: Issues with AutoHotkey v2 LSP + Sublime Text 4, etc.

Post by thqby » 25 Jan 2023, 07:56

You may have missed a few steps.
  • Send the initialize request and wait for the language server initialization to complete.
  • Send initialized notification.
  • Send textDocument/didOpen notification, to open a virtual document.
  • Send textDocument/formatting request.
  • Send textDocument/didClose notification.
The request contains an id and a corresponding response. The notification has no id and no response.

crocodile
Posts: 98
Joined: 28 Dec 2020, 13:41

Re: Issues with AutoHotkey v2 LSP + Sublime Text 4, etc.

Post by crocodile » 25 Jan 2023, 08:43

Yes, I used the same process.

Code: Select all

sendMessage('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: a_ahkpath,
		WorkingDirs: [],
		SymbolFoldingFromOpenBrace: false
	},
	capabilities: {
		textDocument: {
			synchronization: {
				dynamicRegistration: true,
				willSave: true,
				willSaveWaitUntil: true,
				didSave: true
			},
			formatting: { dynamicRegistration: true },
			rangeFormatting: { dynamicRegistration: true },
			onTypeFormatting: { dynamicRegistration: true } }
	}
})
sleep 2000
sendMessage('initialized', , 0)
sleep 2000
sendMessage('textDocument/didOpen', {
	textDocument: {
		uri: 'ahkres:untitled',
		languageId: 'ahk2',
		version: 0,
		text: "MsgBox        33"
	}
}, 0)
sleep 2000
MsgBox sendMessage('textDocument/formatting', {
	textDocument: {
		uri: 'ahkres:untitled'
	},
	options: {
		tabSize: 4,
		toLowerCase: true,
		insertSpaces: true
	}
})

User avatar
thqby
Posts: 408
Joined: 16 Apr 2021, 11:18
Contact:

Re: Issues with AutoHotkey v2 LSP + Sublime Text 4, etc.

Post by thqby » 25 Jan 2023, 10:22

crocodile wrote:
25 Jan 2023, 00:49
Did the call method change, please? I used the call method from the first example and it returned an error message.
In the example of socket mode, there is no implementation to wait for a response and return the result. so msgbox sendmessage(...) is invalid.
The third parameter is used to distinguish between a request and a notification, and when it is true, a notification is sent.

Post Reply

Return to “Editors”