From d3baafab67ff6cf235b5dae9d3cb7c4fca58394f Mon Sep 17 00:00:00 2001 From: Mohammed Elwardi Fadeli <34474472+FoamScience@users.noreply.github.com> Date: Fri, 7 Jan 2022 19:31:37 +0100 Subject: [PATCH] Prepare to auto-install with nvim-lsp-installer (#8) * Remove dependency on native OpenFOAM parser - Allows running in environments where OpenFOAM is not installed - Full dependency on Tree-Sitter grammar for implementing basic LSP features * Switch to using own version of tree-sitter * Use upgraded tree-sitter * Basic support for workspace symbols * Basic support for workspace diagnostics * Update README * Update package-lock * Add textdocument to deps * Fix when keywords auto-completion is triggered * Upgrade deps * Switch to using backend * Correct package.json * Update package-lock.json * Minor bug fixes and better tests * Add tsc to deps * Bump version to 0.1.3 --- .github/workflows/npm-publish.yml | 16 ------ .npmignore | 3 ++ bin/foam-ls | 2 + package-lock.json | 5 +- package.json | 8 +-- .../foamDefinition.ts | 26 ++++++---- tests/languageServer.test.js | 50 ++++++++++++++++++- tsconfig.json | 1 + 8 files changed, 78 insertions(+), 33 deletions(-) create mode 100644 .npmignore create mode 100755 bin/foam-ls diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index d81f0e9..dee6ce3 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -20,19 +20,3 @@ jobs: - run: npm publish env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - - publish-gpr: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 16 - registry-url: https://npm.pkg.github.com/ - - run: npm install - - run: npm publish - env: - NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..485afc9 --- /dev/null +++ b/.npmignore @@ -0,0 +1,3 @@ +.github/** +Release/** +build/** diff --git a/bin/foam-ls b/bin/foam-ls new file mode 100755 index 0000000..eb2fd66 --- /dev/null +++ b/bin/foam-ls @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require("../lib/foam-ls.js"); diff --git a/package-lock.json b/package-lock.json index 71ed7d7..442a71a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,12 @@ { "name": "foam-language-server", - "version": "0.1.0", + "version": "0.1.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "foam-language-server", - "version": "0.1.0", - "hasInstallScript": true, + "version": "0.1.2", "license": "GPL-3.0-or-later", "dependencies": { "tree-sitter-foam": "^0.2.0", diff --git a/package.json b/package.json index c5f2eb1..01f35ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "foam-language-server", - "version": "0.1.0", + "version": "0.1.3", "description": "A language server implementation for OpenFOAM file format.", "keywords": [ "language", @@ -8,10 +8,10 @@ "openfoam" ], "main": "lib/foam-ls.js", + "types": "lib/foam-ls.d.ts", "scripts": { - "install": "tsc --build", + "prepublish": "tsc --build", "clean": "tsc --build --clean", - "wasm": "node test-wasm.js", "test": "jest" }, "author": "Mohammed Elwardi Fadeli", @@ -20,7 +20,7 @@ "node": "*" }, "bin": { - "foam-ls": "./lib/foam-ls.js" + "foam-ls": "./bin/foam-ls" }, "dependencies": { "tree-sitter-foam": "^0.2.0", diff --git a/server/foamfile-language-service/foamDefinition.ts b/server/foamfile-language-service/foamDefinition.ts index 74e1a83..a5cb6cf 100644 --- a/server/foamfile-language-service/foamDefinition.ts +++ b/server/foamfile-language-service/foamDefinition.ts @@ -33,7 +33,7 @@ export class FoamDefinition { } // Returns the macro node at current position - public getNodeUnderCursor(content: string, position: Position) { + public getMacroNodeUnderCursor(content: string, position: Position) { let document : TextDocument = TextDocument.create("", "foam", 0, content); let offset = document.offsetAt(position) const tree = this.treeParser.parse(content); @@ -58,7 +58,7 @@ export class FoamDefinition { } root = closestNode; } - while(root.type != "macro" && root != tree.rootNode) { + while(root != null && root.type != "macro" && root != tree.rootNode) { root = root.parent } return root; @@ -67,7 +67,8 @@ export class FoamDefinition { // Return the node which defines the content of passed node // "node" is assumed to be an indentifier under a macro // i.e. its text starts with "$" - public getNodeDefinition(node: TreeParser.SyntaxNode, content: string) { + // Returns null if current node is not a macro node + public getNodeDefinition(node: TreeParser.SyntaxNode, content: string) : TreeParser.SyntaxNode | null { // Return the same node if not a macro-like if (!node.text.startsWith('$')) { return node; } @@ -83,19 +84,19 @@ export class FoamDefinition { // retrace parents until finding a dict // Issues: // - Tree-Sitter walk does not like parent retracing - let nodeParents = node.text.replace('$:', '').split('.'); + let nodeParents = node == null ? [] : node.text.replace('$:', '').split('.'); // This parameter denotes how much of node parents we've found let prec = 0; // If this is the root node (hopefully), enter the tree - if (cursor.currentNode().type == "foam") { + if (cursor.currentNode() != null && cursor.currentNode().type == "foam") { cursor.gotoFirstChild(); } // Find all dictionaries down to the last dictionary level just before // the matching keyword - while (prec != nodeParents.length-1) { + while (node != null && prec != nodeParents.length-1) { node = cursor.currentNode(); // If this is a dict matching a requested parent if (node.type == "dict" && node.namedChild(0).text == nodeParents[prec]) @@ -109,7 +110,7 @@ export class FoamDefinition { } // Find the matching key-value pair - while (prec != nodeParents.length) { + while (node != null && prec != nodeParents.length) { node = cursor.currentNode(); // If it's a dict_core, skip to the its content if (node.type == "dict_core") { @@ -130,8 +131,15 @@ export class FoamDefinition { // - "Definition" for now means macro expansion public computeDefinition(textDocument: TextDocumentIdentifier, content: string, position: Position): Location | null { - let currentNode = this.getNodeUnderCursor(content, position); - let definitionNode = this.getNodeDefinition(currentNode, content); + let currentMacroNode = this.getMacroNodeUnderCursor(content, position); + if (currentMacroNode == null) { + return Location.create(textDocument.uri, Range.create( + position.line, + position.character, + position.line, + position.character)); + } + let definitionNode = this.getNodeDefinition(currentMacroNode, content); let range = Range.create( definitionNode.startPosition.row, definitionNode.startPosition.column, diff --git a/tests/languageServer.test.js b/tests/languageServer.test.js index 6f39f3d..a7a876f 100755 --- a/tests/languageServer.test.js +++ b/tests/languageServer.test.js @@ -4,6 +4,8 @@ const foamTreeParser = require('../lib/foamfile-language-service/foamTreeParser. // Typical dictionary content for OpenFOAM cases +// If you're testing something new, please append to this dictionary, +// don't change already existing keywords as other tests depend on the position of things here let testContent = ` /*--------------------------------*- C++ -*----------------------------------*\ | ========= | | @@ -83,8 +85,20 @@ test('Get macro keyword definition (Absolute path)', const syms = new parser.FoamDefinition(tp); const expectedSyms = [32, 9]; let keys = syms.computeDefinition("", testContent, lsp.Position.create(34, 20));//.map(a => a.name); + expect([keys.range.start.line, keys.range.start.character]).toEqual(expectedSyms); + } +); + +test('Return current position if definition requested for non-macro nodes', + async () => { + // Test to see if definition for $:tool.list is correctly found + const parser = require('../lib/foamfile-language-service/foamDefinition'); + let tp = await foamTreeParser.getParser(); + const syms = new parser.FoamDefinition(tp); + const expectedSyms = [32, 6]; + let keys = syms.computeDefinition("", testContent, lsp.Position.create(32, 6));//.map(a => a.name); expect('').toBe(''); - //expect([keys.range.start.line, keys.range.start.character]).toEqual(expectedSyms); + expect([keys.range.start.line, keys.range.start.character]).toEqual(expectedSyms); } ); @@ -135,3 +149,37 @@ test('Get Completion item for a keyword', expect(props[0].label).toEqual('type'); } ); + +test('Get Completion item for a keyword value', + async () => { + // Testing completion on "type" keyword when typing "ty" + const parser = require('../lib/foamfile-language-service/foamAssist') + let tp = await foamTreeParser.getParser(); + const markup = require('../lib/foamfile-language-service/foamPlainText'); + const fc = require('../lib/foamfile-language-service/foamCompletion') + const docs = new markup.PlainTextDocumentation(); + const document = lsp.TextDocument.create("", "foam", 0, testContent ); + const capabs = []; + const comps = new parser.FoamAssist(document, capabs, tp); + const resolver = new fc.FoamCompletion(); + props = comps.computeProposals(lsp.Position.create(34,14)); + expect(props[0].label).not.toEqual('type'); + } +); + +test('Get Completion items for a macro on $', + async () => { + // Testing completion on "type" keyword when typing "ty" + const parser = require('../lib/foamfile-language-service/foamAssist') + let tp = await foamTreeParser.getParser(); + const markup = require('../lib/foamfile-language-service/foamPlainText'); + const fc = require('../lib/foamfile-language-service/foamCompletion') + const docs = new markup.PlainTextDocumentation(); + const document = lsp.TextDocument.create("", "foam", 0, testContent ); + const capabs = []; + const comps = new parser.FoamAssist(document, capabs, tp); + const resolver = new fc.FoamCompletion(); + props = comps.computeProposals(lsp.Position.create(34,15)); + expect(props[0].label).toEqual('FoamFile'); + } +); diff --git a/tsconfig.json b/tsconfig.json index dbe7a30..e455cd2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "module": "commonjs", "moduleResolution": "node", "sourceMap": true, + "declaration": true, "lib" : [ "es2016" ], "outDir": "lib" },