Skip to content

Commit feadf62

Browse files
authored
Merge pull request #1 from halcyon-tech/feature/language_tools
Feature/language tools
2 parents 60265f4 + 1facf6b commit feadf62

File tree

9 files changed

+394
-12
lines changed

9 files changed

+394
-12
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
# vscode-db2i
22

3-
Db2 for IBM i tools is a reimplementation of the Schemas tool inside of Access Client Solutions. There is a lot to do so it will not likely be on the Marketplace any time soon.
3+
Db2 for IBM i tools provides SQL functionality to VS Code. It is in preview right now.
4+
5+
* Language tools. List tables and views and their columns when writing SQL, as well as procedures and functions
6+
* Language validator* (must be enabled in VS Code settings)
7+
* Schemas tool* (in preview)
8+
9+
\* *must be enabled in the settings*
410

511
![](./media/main.png)
612

@@ -10,6 +16,7 @@ See the [Projects](https://github.com/halcyon-tech/vscode-db2i/projects) tab to
1016

1117
### Contribution notes
1218

19+
* We need help with the Schemas tool views.
1320
* Each object type in the tree list has a Defintion View (by clicking on the object). Each Definition View exists in it's own folder. For example the table definition exists at `./src/panels/table`. This means other types will get their own folder for their Definition View. Views might be `./src/panels/view` and procedures might be `./src/panels/procedure`.
1421
* Each object type has a class in the `./src/database` folder. For example, the table Definition View has the class `./src/view/table.js` which has all the methods needed to fetch information for that view. You may also add other static methods which could be used for commands specific to the table. Other objects would also get their own class.
1522
* **The best example to work from is `src/panels/view/index.js`.**

package-lock.json

Lines changed: 21 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,33 @@
99
"categories": [
1010
"Other"
1111
],
12+
"publisher": "halcyontechltd",
13+
"author": {
14+
"name": "Halcyon-Tech, Liam Allan"
15+
},
1216
"activationEvents": [
13-
"onView:schemaBrowser"
17+
"onStartupFinished"
1418
],
19+
"extensionDependencies": [
20+
"halcyontechltd.code-for-ibmi"
21+
],
1522
"main": "./src/extension.js",
1623
"contributes": {
24+
"configuration": {
25+
"title": "Db2 for IBM i",
26+
"properties": {
27+
"vscode-db2i.validator": {
28+
"type": "boolean",
29+
"description": "Enable/disable the SQL validator",
30+
"default": false
31+
},
32+
"vscode-db2i.schemas": {
33+
"type": "boolean",
34+
"description": "Enable/disable the Schemas tool (preview)",
35+
"default": false
36+
}
37+
}
38+
},
1739
"viewsContainers": {
1840
"activitybar": [
1941
{
@@ -108,6 +130,7 @@
108130
},
109131
"dependencies": {
110132
"@vscode/webview-ui-toolkit": "^0.8.5",
133+
"node-sql-parser": "^4.1.1",
111134
"sql-formatter": "^4.0.2"
112135
}
113136
}

src/configuration.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
const vscode = require(`vscode`);
3+
4+
module.exports = class Configuration {
5+
/**
6+
* Returns variable not specific to a host (e.g. a global config)
7+
* @param {string} prop
8+
*/
9+
static get(prop) {
10+
const globalData = vscode.workspace.getConfiguration(`vscode-db2i`);
11+
return globalData.get(prop);
12+
}
13+
}

src/database/table.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ module.exports = class Table {
2323
const content = instance.getContent();
2424

2525
return content.runSQL([
26-
`SELECT * FROM QSYS2.SYSCOLUMNS`,
26+
`SELECT * FROM QSYS2.SYSCOLUMNS2`,
2727
`WHERE TABLE_SCHEMA = '${this.schema}' AND TABLE_NAME = '${this.tableName}'`,
2828
`ORDER BY ORDINAL_POSITION`
2929
].join(` `));

src/extension.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
const vscode = require(`vscode`);
44
const schemaBrowser = require(`./views/schemaBrowser`);
55

6+
const Configuration = require(`./configuration`);
7+
8+
const languageProvider = require(`./language/provider`);
9+
610
// this method is called when your extension is activated
711
// your extension is activated the very first time the command is executed
812

@@ -15,12 +19,16 @@ function activate(context) {
1519
// This line of code will only be executed once when your extension is activated
1620
console.log(`Congratulations, your extension "vscode-db2i" is now active!`);
1721

18-
context.subscriptions.push(
19-
vscode.window.registerTreeDataProvider(
20-
`schemaBrowser`,
21-
new schemaBrowser(context)
22-
),
23-
);
22+
if (Configuration.get(`schemas`)) {
23+
context.subscriptions.push(
24+
vscode.window.registerTreeDataProvider(
25+
`schemaBrowser`,
26+
new schemaBrowser(context)
27+
),
28+
);
29+
}
30+
31+
languageProvider.initialise(context);
2432
}
2533

2634
// this method is called when your extension is deactivated

src/language/provider.js

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
const vscode = require(`vscode`);
2+
3+
const { Parser } = require(`node-sql-parser`);
4+
5+
const Store = require(`./store`);
6+
const Configuration = require(`../configuration`);
7+
8+
/** @type {{[path: string]: object}} */
9+
const workingAst = {}
10+
11+
/**
12+
*
13+
* @param {vscode.ExtensionContext} context
14+
*/
15+
exports.initialise = async (context) => {
16+
17+
let editTimeout;
18+
const linterDiagnostics = vscode.languages.createDiagnosticCollection(`SQL Diagnostics`);
19+
20+
context.subscriptions.push(
21+
linterDiagnostics,
22+
23+
vscode.workspace.onDidChangeTextDocument(async (editor) => {
24+
const document = editor.document;
25+
26+
if (document.languageId === `sql`) {
27+
clearTimeout(editTimeout);
28+
29+
const text = document.getText();
30+
if (text.endsWith(`.`)) return;
31+
32+
const parser = new Parser();
33+
try {
34+
const sqlAst = parser.astify(document.getText(), {
35+
database: `DB2`,
36+
});
37+
38+
if (sqlAst) {
39+
workingAst[document.uri.path] = sqlAst;
40+
}
41+
42+
linterDiagnostics.set(document.uri, []);
43+
} catch (e) {
44+
const location = e.location;
45+
46+
if (Configuration.get(`validator`)) {
47+
editTimeout = setTimeout(async () => {
48+
linterDiagnostics.set(document.uri, [{
49+
message: e.message,
50+
range: new vscode.Range(location.start.line-1, location.end.column-1, location.end.line-1, location.end.column-1),
51+
severity: vscode.DiagnosticSeverity.Error,
52+
}]);
53+
}, 600);
54+
}
55+
}
56+
}
57+
}),
58+
59+
vscode.languages.registerCompletionItemProvider({language: `sql` }, {
60+
provideCompletionItems: async (document, position) => {
61+
///** @type vscode.CompletionItem[] */
62+
const items = [];
63+
64+
const ast = workingAst[document.uri.path];
65+
if (ast) {
66+
if (ast.from && ast.from.length > 0) {
67+
ast.from.forEach(definedAs => {
68+
const item = new vscode.CompletionItem(definedAs.as || definedAs.table, vscode.CompletionItemKind.Struct);
69+
item.detail = `${definedAs.db}.${definedAs.table}`;
70+
items.push(item);
71+
});
72+
}
73+
};
74+
75+
return items;
76+
}
77+
}, ` `)
78+
),
79+
80+
vscode.languages.registerCompletionItemProvider({language: `sql` }, {
81+
provideCompletionItems: async (document, position) => {
82+
///** @type vscode.CompletionItem[] */
83+
const items = [];
84+
85+
if (!Store.hasConnection()) return [];
86+
87+
const currentPosition = new vscode.Position(position.line, position.character - 1);
88+
const range = document.getWordRangeAtPosition(currentPosition);
89+
90+
const prefix = range ? document.getText(range) : null;
91+
92+
let fallbackLookup = false;
93+
94+
const baseAst = workingAst[document.uri.path];
95+
96+
let astList = [];
97+
if (Array.isArray(baseAst)) astList = baseAst;
98+
else if (baseAst) astList = [baseAst];
99+
100+
if (prefix) {
101+
for (const ast of astList) {
102+
fallbackLookup = false;
103+
104+
if (ast) {
105+
if (ast.from && ast.from.length > 0) {
106+
const definedAs =
107+
ast.from.find(f => f.as === prefix) ||
108+
ast.from.find(f => f.table === prefix);
109+
110+
if (definedAs) {
111+
112+
if (definedAs.db) {
113+
const columns = await Store.getColumns(definedAs.db, definedAs.table);
114+
115+
columns.forEach(column => {
116+
const item = new vscode.CompletionItem(column.COLUMN_NAME.toLowerCase(), vscode.CompletionItemKind.Field);
117+
item.insertText = new vscode.SnippetString(column.COLUMN_NAME.toLowerCase());
118+
item.detail = column.DATA_TYPE;
119+
item.documentation = new vscode.MarkdownString(`${column.COLUMN_TEXT} (\`${definedAs.db}.${definedAs.table}\`)`);
120+
items.push(item);
121+
});
122+
} else {
123+
const objects = await Store.getObjects(prefix);
124+
125+
objects.forEach(object => {
126+
let type;
127+
128+
switch (object.TABLE_TYPE) {
129+
case `T`: type = `Table`; break;
130+
case `V`: type = `View`; break;
131+
case `P`: type = `Table`; break;
132+
}
133+
134+
const item = new vscode.CompletionItem(object.TABLE_NAME.toLowerCase(), vscode.CompletionItemKind.Struct);
135+
item.insertText = new vscode.SnippetString(object.TABLE_NAME.toLowerCase());
136+
item.detail = type;
137+
item.documentation = object.TABLE_TEXT;
138+
items.push(item);
139+
});
140+
141+
const routines = await Store.routinesAvailable(prefix);
142+
143+
if (routines) {
144+
routines
145+
.filter(proc => proc.type === `FUNCTION`)
146+
.forEach(proc => {
147+
const item = new vscode.CompletionItem(proc.name.toLowerCase(), proc.type === `PROCEDURE` ? vscode.CompletionItemKind.Method : vscode.CompletionItemKind.Function);
148+
item.insertText = new vscode.SnippetString(`${proc.name.toLowerCase()}(${proc.parameters.map((parm, index) => `\${${index+1}:${parm.name}}`).join(`, `)})\$0`)
149+
item.detail = `${proc.schema}.${proc.name} ${proc.type}`;
150+
item.documentation = new vscode.MarkdownString(`${proc.comment} (\`${proc.externalName}\`)`);
151+
items.push(item);
152+
});
153+
}
154+
}
155+
}
156+
}
157+
158+
if (ast.type && ast.type === `call`) {
159+
fallbackLookup = true;
160+
}
161+
}
162+
163+
if (fallbackLookup) {
164+
const routines = await Store.getRoutines(prefix);
165+
166+
routines.forEach(proc => {
167+
const item = new vscode.CompletionItem(proc.name.toLowerCase(), proc.type === `PROCEDURE` ? vscode.CompletionItemKind.Method : vscode.CompletionItemKind.Function);
168+
item.insertText = new vscode.SnippetString(`${proc.name.toLowerCase()}(${proc.parameters.map((parm, index) => `\${${index+1}:${parm.name}}`).join(`:`)})\$0`)
169+
item.detail = `${proc.schema}.${proc.name} ${proc.type}`;
170+
item.documentation = new vscode.MarkdownString(`${proc.comment} (\`${proc.externalName}\`)`);
171+
items.push(item);
172+
});
173+
}
174+
};
175+
}
176+
177+
return items;
178+
}
179+
}, `.`)
180+
}

0 commit comments

Comments
 (0)