@neo4j-cypher/language-server 2.0.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/LICENSE.md +201 -0
- package/README.md +146 -0
- package/dist/cypher-language-server.js +93 -0
- package/package.json +48 -0
- package/src/autocompletion.ts +34 -0
- package/src/server.ts +124 -0
- package/src/signatureHelp.ts +39 -0
- package/src/syntaxColouring.ts +39 -0
- package/src/types.ts +10 -0
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@neo4j-cypher/language-server",
|
|
3
|
+
"description": "Cypher Language Server",
|
|
4
|
+
"author": "Neo4j Inc.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"files": [
|
|
7
|
+
"./dist/cypher-language-server.js",
|
|
8
|
+
"./src",
|
|
9
|
+
"package.json",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE.md",
|
|
12
|
+
"CHANGELOG.md"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"neo4j",
|
|
16
|
+
"cypher",
|
|
17
|
+
"language server"
|
|
18
|
+
],
|
|
19
|
+
"version": "2.0.0-next.0",
|
|
20
|
+
"main": "./dist/cypher-language-server.js",
|
|
21
|
+
"types": "src/server.ts",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git://github.com/neo4j/cypher-language-support.git"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/neo4j/cypher-language-support/issues"
|
|
28
|
+
},
|
|
29
|
+
"engineStrict": true,
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18.18.2"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@neo4j-cypher/language-support": "2.0.0-next.0",
|
|
35
|
+
"neo4j-driver": "^5.3.0",
|
|
36
|
+
"vscode-languageserver": "^8.1.0",
|
|
37
|
+
"vscode-languageserver-textdocument": "^1.0.8"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc -b && npm run bundle",
|
|
41
|
+
"bundle": "esbuild ./src/server.ts --bundle --format=cjs --platform=node --outfile=dist/cypher-language-server.js",
|
|
42
|
+
"clean": "rm -rf dist",
|
|
43
|
+
"watch": "tsc -b -w"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"esbuild": "^0.19.4"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Position,
|
|
3
|
+
TextDocumentPositionParams,
|
|
4
|
+
TextDocuments,
|
|
5
|
+
} from 'vscode-languageserver/node';
|
|
6
|
+
|
|
7
|
+
import { Range } from 'vscode-languageserver-types';
|
|
8
|
+
|
|
9
|
+
import { autocomplete } from '@neo4j-cypher/language-support';
|
|
10
|
+
import { Neo4jSchemaPoller } from '@neo4j-cypher/schema-poller';
|
|
11
|
+
import { TextDocument } from 'vscode-languageserver-textdocument';
|
|
12
|
+
|
|
13
|
+
export function doAutoCompletion(
|
|
14
|
+
documents: TextDocuments<TextDocument>,
|
|
15
|
+
neo4j: Neo4jSchemaPoller,
|
|
16
|
+
) {
|
|
17
|
+
return (textDocumentPosition: TextDocumentPositionParams) => {
|
|
18
|
+
const textDocument = documents.get(textDocumentPosition.textDocument.uri);
|
|
19
|
+
if (textDocument === undefined) return [];
|
|
20
|
+
|
|
21
|
+
const position: Position = textDocumentPosition.position;
|
|
22
|
+
const range: Range = {
|
|
23
|
+
// TODO Nacho: We are parsing from the begining of the file.
|
|
24
|
+
// Do we need to parse from the begining of the current query?
|
|
25
|
+
start: Position.create(0, 0),
|
|
26
|
+
end: position,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return autocomplete(
|
|
30
|
+
textDocument.getText(range),
|
|
31
|
+
neo4j.metadata?.dbSchema ?? {},
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
}
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createConnection,
|
|
3
|
+
DidChangeConfigurationNotification,
|
|
4
|
+
InitializeResult,
|
|
5
|
+
ProposedFeatures,
|
|
6
|
+
SemanticTokensRegistrationOptions,
|
|
7
|
+
SemanticTokensRegistrationType,
|
|
8
|
+
TextDocuments,
|
|
9
|
+
TextDocumentSyncKind,
|
|
10
|
+
} from 'vscode-languageserver/node';
|
|
11
|
+
|
|
12
|
+
import { TextDocument } from 'vscode-languageserver-textdocument';
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
syntaxColouringLegend,
|
|
16
|
+
validateSyntax,
|
|
17
|
+
} from '@neo4j-cypher/language-support';
|
|
18
|
+
import { Neo4jSchemaPoller } from '@neo4j-cypher/schema-poller';
|
|
19
|
+
import { doAutoCompletion } from './autocompletion';
|
|
20
|
+
import { doSignatureHelp } from './signatureHelp';
|
|
21
|
+
import { applySyntaxColouringForDocument } from './syntaxColouring';
|
|
22
|
+
import { Neo4jSettings } from './types';
|
|
23
|
+
|
|
24
|
+
const connection = createConnection(ProposedFeatures.all);
|
|
25
|
+
|
|
26
|
+
// Create a simple text document manager.
|
|
27
|
+
const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);
|
|
28
|
+
|
|
29
|
+
const neo4jSdk = new Neo4jSchemaPoller();
|
|
30
|
+
|
|
31
|
+
connection.onInitialize(() => {
|
|
32
|
+
const result: InitializeResult = {
|
|
33
|
+
capabilities: {
|
|
34
|
+
textDocumentSync: TextDocumentSyncKind.Full,
|
|
35
|
+
// Tell the client what features does the server support
|
|
36
|
+
completionProvider: {
|
|
37
|
+
resolveProvider: false,
|
|
38
|
+
triggerCharacters: ['.'],
|
|
39
|
+
},
|
|
40
|
+
semanticTokensProvider: {
|
|
41
|
+
documentSelector: null,
|
|
42
|
+
legend: syntaxColouringLegend,
|
|
43
|
+
range: false,
|
|
44
|
+
full: {
|
|
45
|
+
delta: false,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
signatureHelpProvider: {
|
|
49
|
+
triggerCharacters: ['(', ',', ')'],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return result;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
connection.onInitialized(() => {
|
|
58
|
+
void connection.client.register(DidChangeConfigurationNotification.type, {
|
|
59
|
+
section: 'neo4j',
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const registrationOptions: SemanticTokensRegistrationOptions = {
|
|
63
|
+
documentSelector: null,
|
|
64
|
+
legend: syntaxColouringLegend,
|
|
65
|
+
range: false,
|
|
66
|
+
full: {
|
|
67
|
+
delta: false,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
void connection.client.register(
|
|
71
|
+
SemanticTokensRegistrationType.type,
|
|
72
|
+
registrationOptions,
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Trigger the syntactic errors highlighting on every document change
|
|
77
|
+
documents.onDidChangeContent((change) => {
|
|
78
|
+
const document = change.document;
|
|
79
|
+
const diagnostics = validateSyntax(
|
|
80
|
+
document.getText(),
|
|
81
|
+
neo4jSdk.metadata?.dbSchema ?? {},
|
|
82
|
+
);
|
|
83
|
+
void connection.sendDiagnostics({
|
|
84
|
+
uri: document.uri,
|
|
85
|
+
diagnostics: diagnostics,
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Trigger the syntax colouring
|
|
90
|
+
connection.languages.semanticTokens.on(
|
|
91
|
+
applySyntaxColouringForDocument(documents),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Trigger the signature help, providing info about functions / procedures
|
|
95
|
+
connection.onSignatureHelp(doSignatureHelp(documents, neo4jSdk));
|
|
96
|
+
// Trigger the auto completion
|
|
97
|
+
connection.onCompletion(doAutoCompletion(documents, neo4jSdk));
|
|
98
|
+
|
|
99
|
+
connection.onDidChangeConfiguration(
|
|
100
|
+
(params: { settings: { neo4j: Neo4jSettings } }) => {
|
|
101
|
+
neo4jSdk.disconnect();
|
|
102
|
+
|
|
103
|
+
const neo4jConfig = params.settings.neo4j;
|
|
104
|
+
if (
|
|
105
|
+
neo4jSdk.connection === undefined &&
|
|
106
|
+
neo4jConfig.connect &&
|
|
107
|
+
neo4jConfig.password &&
|
|
108
|
+
neo4jConfig.URL &&
|
|
109
|
+
neo4jConfig.user
|
|
110
|
+
) {
|
|
111
|
+
void neo4jSdk.persistentConnect(
|
|
112
|
+
neo4jConfig.URL,
|
|
113
|
+
{
|
|
114
|
+
username: neo4jConfig.user,
|
|
115
|
+
password: neo4jConfig.password,
|
|
116
|
+
},
|
|
117
|
+
{ appName: 'cypher-language-server' },
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
documents.listen(connection);
|
|
124
|
+
connection.listen();
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { signatureHelp } from '@neo4j-cypher/language-support';
|
|
2
|
+
import {
|
|
3
|
+
Position,
|
|
4
|
+
Range,
|
|
5
|
+
SignatureHelp,
|
|
6
|
+
SignatureHelpParams,
|
|
7
|
+
TextDocuments,
|
|
8
|
+
} from 'vscode-languageserver/node';
|
|
9
|
+
|
|
10
|
+
import { Neo4jSchemaPoller } from '@neo4j-cypher/schema-poller';
|
|
11
|
+
import { TextDocument } from 'vscode-languageserver-textdocument';
|
|
12
|
+
|
|
13
|
+
export const emptyResult: SignatureHelp = {
|
|
14
|
+
signatures: [],
|
|
15
|
+
activeSignature: undefined,
|
|
16
|
+
activeParameter: undefined,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function doSignatureHelp(
|
|
20
|
+
documents: TextDocuments<TextDocument>,
|
|
21
|
+
neo4j: Neo4jSchemaPoller,
|
|
22
|
+
) {
|
|
23
|
+
return (params: SignatureHelpParams) => {
|
|
24
|
+
const textDocument = documents.get(params.textDocument.uri);
|
|
25
|
+
const endOfTriggerHelp = params.context?.triggerCharacter === ')';
|
|
26
|
+
if (textDocument === undefined || endOfTriggerHelp) return emptyResult;
|
|
27
|
+
|
|
28
|
+
const position = params.position;
|
|
29
|
+
const range: Range = {
|
|
30
|
+
start: Position.create(0, 0),
|
|
31
|
+
end: position,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return signatureHelp(
|
|
35
|
+
textDocument.getText(range),
|
|
36
|
+
neo4j.metadata?.dbSchema ?? {},
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applySyntaxColouring,
|
|
3
|
+
mapCypherToSemanticTokenIndex,
|
|
4
|
+
} from '@neo4j-cypher/language-support';
|
|
5
|
+
import {
|
|
6
|
+
SemanticTokensBuilder,
|
|
7
|
+
SemanticTokensParams,
|
|
8
|
+
TextDocument,
|
|
9
|
+
TextDocuments,
|
|
10
|
+
} from 'vscode-languageserver';
|
|
11
|
+
|
|
12
|
+
export function applySyntaxColouringForDocument(
|
|
13
|
+
documents: TextDocuments<TextDocument>,
|
|
14
|
+
) {
|
|
15
|
+
return (params: SemanticTokensParams) => {
|
|
16
|
+
const textDocument = documents.get(params.textDocument.uri);
|
|
17
|
+
if (textDocument === undefined) return { data: [] };
|
|
18
|
+
|
|
19
|
+
const tokens = applySyntaxColouring(textDocument.getText());
|
|
20
|
+
|
|
21
|
+
const builder = new SemanticTokensBuilder();
|
|
22
|
+
|
|
23
|
+
tokens.forEach((token) => {
|
|
24
|
+
const tokenColour = mapCypherToSemanticTokenIndex(token.tokenType);
|
|
25
|
+
|
|
26
|
+
if (tokenColour !== undefined) {
|
|
27
|
+
builder.push(
|
|
28
|
+
token.position.line,
|
|
29
|
+
token.position.startCharacter,
|
|
30
|
+
token.length,
|
|
31
|
+
tokenColour,
|
|
32
|
+
0,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
const results = builder.build();
|
|
37
|
+
return results;
|
|
38
|
+
};
|
|
39
|
+
}
|