Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added experimental language server #3635

Open
wants to merge 29 commits into
base: master
Choose a base branch
from

Conversation

Tomatower
Copy link
Contributor

@Tomatower Tomatower commented Jan 27, 2021

I have implemented a base structure for Microsofts Language Server Protocol (https://langserver.org).

Alongside the basic protocol parsing and error message generation it currently only supports 2 things:

  1. Click-to-code: with Visual Studio Code (Clicking in Openscad opens and selects the respective file in VSCode)
  2. Start a preview: (only the protocol - not the internas yet)

This PR has to be completed with a lot more implementations: such as utilizing the AST-Trees for context sensitive autocomplete information and keeping track of open and modified fieles in vscode in order to render them without saving.

I would like to get some feedback on how I integrated it into the remaining codebase so far, and maybe somebody has more ideas on how we can control the openscad window from an external editor.

Open Questions

  1. Currently It will attach the LanguageServerInterface to the first opened window. Is that a limitation?
  2. Should the featureset of an openscad window opened that way (most likely from an editor) be limited (no editor / no saving / no multiple windows in a process)
  3. What information should be transferred to the editor?

ToDo

  • LSP Mode (activated by the --lsp-* flag)
    • Disable Editor
    • Disable automatic reload and preview
  • Add Messages (references to https://microsoft.github.io/language-server-protocol/specifications/specification-current/)
    • Click to Code into Editor: window/showDocument
    • Text Document sync: textDocument/didOpen, textDocument/didChange, textDocument/didClose
    • Send results of the AST-Parsing via textDocument/publishDiagnostics
    • Send the OpenSCAD log window/logMessage
    • Get Document Symbols textDocument/documentSymbol (required for Netbeans)
  • Add a stdio-interface with --lsp-stdio cmdline switch
  • Add server-inteface with --lsp-listen PORT cmdline switch
  • Add client-interface (following the spec) with --lsp-connect PORT
  • Add standard-Conform TCP socket
  • Add $openscad/preview
  • Set workspace path as PWD
  • Assert Failure with customizer

followed up by #3648
closes #2629

@Tomatower Tomatower force-pushed the language-server branch 2 times, most recently from bc3011e to 8c3cd79 Compare February 1, 2021 02:09
@Tomatower
Copy link
Contributor Author

Tomatower commented Feb 2, 2021

❎ Netbeans

Language Server on Netbeans (currently no preview support, this will unfortunately most likely require a custom plugin):

  • search field in the top right corner (close all open files if it does not show up)
  • search for Connect
  • select Connect to Language Server
  • root: project root, port the port you used to start openscad with (--lsp-port 23725) extension: .scad
  • Open a SCAD file, and keep an eye on the terminal output of openscad for issues

State of integration

  • not supported Text Document sync (textDocument/didOpen etc)
  • not supported Go To Code (window/showDocument)
  • not supported Does not send updates, so we dont know what is open (textDocument/publishDiagnostics)
  • test pending OpenSCAD log messages (window/logMessage)

means the default LanguageServer implementation of Netbeans is very limited for the Moment.

Quirks

  • Netbeans does not send a initialized message after sucessful setup.
  • Does have a very limited capability set (only "go to definition" and tracking the workspace files)

@Tomatower
Copy link
Contributor Author

Tomatower commented Feb 3, 2021

✔️ Visual Studio Code

  1. checkout & compile
  2. start openscad --lsp-port 23725 (23725 is 0x5CAD)
  3. get the prototype VSCode Extension from https://github.com/Tomatower/vscode-openscad (which is just the example implementation modified for TCP)
  4. Launch with the extension (Please extension authors - integrate that)
  5. Open a model in this openscad window
  6. Right click somewhere, and jump to the code. Both VSCode and Openscad should now have opened the file.

State of Integration

  • Working Text Document sync (textDocument/didOpen etc)
  • Working Go To Code (window/showDocument)
  • Working Diagnostics: creates red squigglies (textDocument/publishDiagnostics)
  • Working test pending OpenSCAD log messages (window/logMessage)

@Tomatower
Copy link
Contributor Author

Tomatower commented Feb 3, 2021

✔️ vim

Installation: Using vim Ale (https://github.com/dense-analysis/ale) with added SCAD language (https://github.com/Tomatower/ale/tree/lsp-openscad NOT MAINTAINED! - will have to do a PR after this is merged.)
do set filetype=scad if ALEInfo does not detect the filetype automatically.

State of Integration

  • Working Text Document sync (textDocument/didOpen etc)
  • 🟨 Not Supported Go To Code (window/showDocument)
  • Working Diagnostics (textDocument/publishDiagnostics)
  • Working OpenSCAD log messages (window/logMessage) (altought only one line at a time in the vim log)

@Tomatower
Copy link
Contributor Author

Tomatower commented Feb 3, 2021

✔️ emacs

Installation: Use this fork of lsp-mode: https://github.com/Tomatower/lsp-mode/tree/client-openscad (upstream is pending to merging this PR)

State of Integration

Thank you @Lenbok for the emacs integration!

@Tomatower
Copy link
Contributor Author

Tomatower commented Feb 3, 2021

✔️ atom

Installation: Somehow hack together a minimal LSP server, either as host or as client mode. If you ask me nicely I can give some source code. But it is barely working.

State of Integration

  • Working Text Document sync (textDocument/didOpen etc)
  • 🟨 Not Supported Go To Code (window/showDocument) [ might be because my plugin did not implement it?]
  • Working Diagnostics (textDocument/publishDiagnostics)
  • Working OpenSCAD log messages (window/logMessage)

@Tomatower Tomatower changed the title RFC: added experimental language server Added experimental language server Feb 7, 2021
@Tomatower
Copy link
Contributor Author

Tomatower commented Feb 9, 2021

🟡 Netbeans (with plugin)

State of integration

  • Working Text Document sync (textDocument/didOpen etc)
  • Not Supported Go To Code (window/showDocument)
  • Not showing up Diagnostics (textDocument/publishDiagnostics)
  • Not showing up OpenSCAD log messages (window/logMessage)

means the LanguageServer implementation of Netbeans is very limited for the Moment.
The Editor might also benefit form putting more effort into the Plugin, if someone has the time to spare.

Quirks

  • Netbeans does not send a initialized message after sucessful setup.
  • Netbeans ignores our capability set and announces a bogus capability set, which has no connection to the actually implemented features.
  • Does have a very limited capability set (only "go to definition" and tracking the workspace files)

@Lenbok
Copy link
Contributor

Lenbok commented Feb 11, 2021

I can help test any emacs lsp integration

@Lenbok
Copy link
Contributor

Lenbok commented Feb 11, 2021

BTW, I've currently been using this small emacs extension I wrote for controlling the openscad gui from within emacs: https://github.com/Lenbok/scad-dbus - it uses DBUS for the IPC mechanism (linux only), but if your LSP server provides for equivalent control of the scad GUI that would probably be a step forward for other platforms. Is that within scope of an LSP server?

@Tomatower
Copy link
Contributor Author

UI control, which is provided via DBUS is not scope of the LSP.
The LSP is designed for things like autocomplete, context information and error reporting/highlighting.

The dbus interface will still be usable if you use the LSP.

I would be very happy if someone can help me out in testing / developing the extension for emacs lsp-mode (so far I have no working LSP integration for emacs)
My current state is: https://github.com/Tomatower/lsp-mode/tree/client-openscad

@Lenbok
Copy link
Contributor

Lenbok commented Feb 12, 2021

Yeah, I thought that UI manipulation would probably be out of scope for an LSP server, but saw you had some comments about it in the PR.

I will try and have a play with your preliminary code over the weekend, thanks for the pointer!

@Lenbok
Copy link
Contributor

Lenbok commented Mar 6, 2021

@Tomatower I figured how how to send the $openscad/preview message and that it wanted the uri of the file to preview. However, I get the response: "File was not opened!", see below for the captured stderr - note that the document was opened immediately previously.

Method mapping:
	initialize 	 --> InitializeRequest
	initialized 	 --> InitializedNotifiy
	shutdown 	 --> ShutdownRequest
	exit 	 --> ExitNotification
	textDocument/didOpen 	 --> DidOpenTextDocument
	textDocument/didChange 	 --> DidChangeTextDocument
	textDocument/didClose 	 --> DidCloseTextDocument
	textDocument/documentSymbol 	 --> DocumentSymbolRequest
	$openscad/preview 	 --> OpenSCADRender
Listening on port 10007
Received connection from :54092
RECEIVED: [2193]: {"jsonrpc":"2.0","method":"initialize","params":{"processId":null,"rootPath":"/home2/len/objects/scad/mine/foo","clientInfo":{"name":"emacs","version":"GNU Emacs 27.1 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.24.20)\n of 2020-09-20"},"rootUri":"file:///home2/len/objects/scad/mine/foo","capabilities":{"workspace":{"workspaceEdit":{"documentChanges":true,"resourceOperations":["create","rename","delete"]},"applyEdit":true,"symbol":{"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]}},"executeCommand":{"dynamicRegistration":false},"didChangeWatchedFiles":{"dynamicRegistration":true},"workspaceFolders":true,"configuration":true},"textDocument":{"declaration":{"linkSupport":true},"definition":{"linkSupport":true},"implementation":{"linkSupport":true},"typeDefinition":{"linkSupport":true},"synchronization":{"willSave":true,"didSave":true,"willSaveWaitUntil":true},"documentSymbol":{"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"hierarchicalDocumentSymbolSupport":true},"formatting":{"dynamicRegistration":true},"rangeFormatting":{"dynamicRegistration":true},"rename":{"dynamicRegistration":true,"prepareSupport":true},"codeAction":{"dynamicRegistration":true,"isPreferredSupport":true,"codeActionLiteralSupport":{"codeActionKind":{"valueSet":["","quickfix","refactor","refactor.extract","refactor.inline","refactor.rewrite","source","source.organizeImports"]}},"resolveSupport":{"properties":["edit","command"]},"dataSupport":true},"completion":{"completionItem":{"snippetSupport":true,"documentationFormat":["markdown"],"resolveAdditionalTextEditsSupport":true},"contextSupport":true},"signatureHelp":{"signatureInformation":{"parameterInformation":{"labelOffsetSupport":true}}},"documentLink":{"dynamicRegistration":true,"tooltipSupport":true},"hover":{"contentFormat":["markdown","plaintext"]},"foldingRange":{"dynamicRegistration":true},"callHierarchy":{"dynamicRegistration":false},"publishDiagnostics":{"relatedInformation":true,"tagSupport":{"valueSet":[1,2]},"versionSupport":true}},"window":{"workDoneProgress":true}},"initializationOptions":null,"workDoneToken":"1"},"id":169}

Handling Message [id 169] with method initialize
SENDING: [204]: {"id":169,"jsonrpc":"2.0","result":{"capabilities":{"documentSymbolProvider":true,"openscad":{"preview":true},"textDocumentSync":{"change":1,"openClose":true},"window":{"showDocument":{"support":true}}}}}
RECEIVED: [53]: {"jsonrpc":"2.0","method":"initialized","params":{}}

Handling Message [id <UNSET>] with method initialized
RECEIVED: [273]: {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///home2/len/objects/scad/mine/foo/bar.scad","languageId":"openscad","version":16,"text":"\nuse</home2/len/objects/scad/mine/foo/baz.scad>\n\nbaz();\nbaz();\n\ncube([10, 10, 10]);\n"}}}

Handling Message [id <UNSET>] with method textDocument/didOpen
SENDING: [145]: {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"diagnostics":[],"uri":"file:///home2/len/objects/scad/mine/foo/bar.scad"}}
RECEIVED: [141]: {"jsonrpc":"2.0","method":"$openscad/preview","params":{"textDocument":{"uri":"file:///home2/len/objects/scad/mine/foo/bar.scad"}},"id":170}

Handling Message [id 170] with method $openscad/preview
SENDING: [83]: {"error":{"code":-32602,"message":"File was not opened!"},"id":170,"jsonrpc":"2.0"}

@Tomatower
Copy link
Contributor Author

thanks for the test, I will look into it!

@Lenbok
Copy link
Contributor

Lenbok commented Mar 10, 2021

I saw your force push and had another try - the extra logging helped. Looks like I was sending the preview with the incorrect message structure (you can see in my paste above, I had the uri inside a textDocument element). Once I rearranged the params correctly it previews.

However, even though it shows the preview, I noticed that the openscad app itself doesn't seem to know the file is loaded. By which I mean if I then switch to the GUI and tell it to do a final render (and export stl, etc) - it still thinks it's looking at an empty "Untitled.scad" and the console warns: "No top level geometry to render". Do you have a suggestion?

BTW, You can update the "Emacs" section above to indicate that TextDocument sync, and Diagnostics are working. Go To Code isn't currently implemented in lsp-mode (emacs-lsp/lsp-mode#2459). Log Messages seem to be currently disabled in your PR? (I also noticed that your initial checkbox in the PR description mentions a --lsp-port, which doesn't exist, which some may find confusing).

This is coming along nicely, and is already quite handy!

@Tomatower
Copy link
Contributor Author

Thanks - glad it helped, I did not have the time to test this enough to tell you!

As you can see in the message log, the language server client (emacs) is sending the document content to openscad in a textDocument/didChange message. This content is then kept inside of the language server implementation and is not in the openscad editor.
All openscad functionality, like openscad-side F5/F6/export/... is done with the editor - and since I never feed a textDocument/didOpen into the editor, the whole front end does not know about stuff.

This was a deliberate decision, since opening the file in openscad opens pandoras box of parallel editing the same document in two editors - which I wanted to avoid.
I think i have to split the openscad internal logic from the editor, so I can force the LSP-document for rendering/preview if triggered from the application itself, I already have ideas.

@Lenbok
Copy link
Contributor

Lenbok commented Mar 11, 2021

I should also mention that I don't like the fact that when you run openscad as an LSP server, it permanently overwrites the user's preference for whether to show the editor and use automatic reload and preview. This makes it hard to switch between using the LSP server and using openscad standalone. It should only alter those settings in a non-persistent manner.

@Lenbok
Copy link
Contributor

Lenbok commented Jun 20, 2021

@Tomatower Have you had any time to take this feature any further?

@Tomatower
Copy link
Contributor Author

I did not yet have any real issues with my own setup (where I use openscad as LSP + vscode), but I did not yet have the time to fix up the preferences-issue you pointed out.

@Lenbok
Copy link
Contributor

Lenbok commented Jun 20, 2021

Can you describe your workflow in more detail, in terms of where openscad gets started, when and how you trigger previews / renders / STL exports etc, and how that works for multi-file projects?

Here's my workflow prior to this PR:

  • I open a scad file in emacs, and from there I have a key bind that can start openscad for the current file. I have openscad to auto-reload, so that whenever I save the file (or other ones referenced by it), openscad updates. In addition, I use https://github.com/Lenbok/scad-dbus so that for most operations I don't even need to leave emacs.

Here's the workflow I've been using with this PR:

  • I open the scad file in emacs, and lsp-mode starts the openscad GUI with lsp server. I like the automatic preview on save (this handles knowing when to redisplay for multi-file projects) so rather than issuing the $openscad/preview command on a "master scad file", I minimize the GUI that was opened by lsp and start a new one as above. (But currently I have to re-enable the option that the lsp-server has disabled).

With this workflow it would be nice if the lsp-server didn't mess with the user preferences (and even better if it could start headless without a GUI at all). Alternatively I could just use the GUI started by lsp-server and hook into emacs so that it had a notion of the "primary scad file" and whenever any scad file in the project was saved it issued the $openscad/preview to get the latest update. That doesn't help with the inability to do render / export etc though.

Do you have any suggestions?

@ochafik
Copy link
Contributor

ochafik commented Mar 12, 2022

Not sure what's missing here, but would be amazing to be able to use this in the OpenSCAD Web Demo (which uses the Monaco editor + some dirty regexps for completion right now).

@Lenbok
Copy link
Contributor

Lenbok commented Mar 12, 2022

As an alternative take on a language server for openscad, see https://github.com/dzhu/openscad-language-server (it doesn't require openscad itself).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Language support for popular code editors via Language Server Protocol
3 participants