mirror of
https://github.com/dbgate/dbgate
synced 2024-11-08 04:35:58 +00:00
code completion
This commit is contained in:
parent
b7b9dde5ae
commit
d4989c75ca
@ -9,11 +9,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import AceEditor from './AceEditor.svelte';
|
import AceEditor from './AceEditor.svelte';
|
||||||
import * as ace from 'ace-builds/src-noconflict/ace';
|
import * as ace from 'ace-builds/src-noconflict/ace';
|
||||||
|
import useEffect from '../utility/useEffect';
|
||||||
|
import { getContext } from 'svelte';
|
||||||
|
import { mountCodeCompletion } from './codeCompletion';
|
||||||
export let engine;
|
export let engine;
|
||||||
|
export let conid;
|
||||||
|
export let database;
|
||||||
|
export let readOnly;
|
||||||
|
|
||||||
let domEditor;
|
let domEditor;
|
||||||
|
|
||||||
let mode;
|
let mode;
|
||||||
|
|
||||||
|
const tabVisible: any = getContext('tabVisible');
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
const match = (engine || '').match(/^([^@]*)@/);
|
const match = (engine || '').match(/^([^@]*)@/);
|
||||||
mode = engineToMode[match ? match[1] : engine] || 'sql';
|
mode = engineToMode[match ? match[1] : engine] || 'sql';
|
||||||
@ -22,6 +31,25 @@
|
|||||||
export function getEditor(): ace.Editor {
|
export function getEditor(): ace.Editor {
|
||||||
return domEditor.getEditor();
|
return domEditor.getEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: effect = useEffect(() => {
|
||||||
|
const editor = domEditor?.getEditor();
|
||||||
|
if ($tabVisible && conid && database && !readOnly && editor) {
|
||||||
|
return mountCodeCompletion({ conid, database, editor });
|
||||||
|
}
|
||||||
|
return () => {};
|
||||||
|
});
|
||||||
|
$: $effect;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AceEditor {mode} {...$$props} on:input on:focus on:blur bind:this={domEditor} />
|
<AceEditor
|
||||||
|
{mode}
|
||||||
|
{...$$props}
|
||||||
|
on:input
|
||||||
|
on:focus
|
||||||
|
on:blur
|
||||||
|
bind:this={domEditor}
|
||||||
|
options={{
|
||||||
|
enableBasicAutocompletion: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
38
packages/web/src/query/analyseQuerySources.ts
Normal file
38
packages/web/src/query/analyseQuerySources.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
export default function analyseQuerySources(sql, sourceNames) {
|
||||||
|
const upperSourceNames = sourceNames.map(x => x.toUpperCase());
|
||||||
|
const tokens = sql.split(/\s+/);
|
||||||
|
const res = [];
|
||||||
|
for (let i = 0; i < tokens.length; i += 1) {
|
||||||
|
const lastWordMatch = tokens[i].match(/([^.]+)$/);
|
||||||
|
if (lastWordMatch) {
|
||||||
|
const word = lastWordMatch[1];
|
||||||
|
const wordUpper = word.toUpperCase();
|
||||||
|
if (upperSourceNames.includes(wordUpper)) {
|
||||||
|
const preWord = tokens[i - 1];
|
||||||
|
if (preWord && /^((join)|(from)|(update)|(delete)|(insert))$/i.test(preWord)) {
|
||||||
|
let postWord = tokens[i + 1];
|
||||||
|
if (postWord && /^as$/i.test(postWord)) {
|
||||||
|
postWord = tokens[i + 2];
|
||||||
|
}
|
||||||
|
if (!postWord) {
|
||||||
|
res.push({
|
||||||
|
name: word,
|
||||||
|
});
|
||||||
|
} else if (
|
||||||
|
/^((where)|(inner)|(left)|(right)|(on)|(join))$/i.test(postWord) ||
|
||||||
|
!/^[a-zA-Z][a-zA-Z0-9]*$/i.test(postWord)
|
||||||
|
) {
|
||||||
|
res.push({
|
||||||
|
name: word,
|
||||||
|
});
|
||||||
|
} else
|
||||||
|
res.push({
|
||||||
|
name: word,
|
||||||
|
alias: postWord,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
122
packages/web/src/query/codeCompletion.ts
Normal file
122
packages/web/src/query/codeCompletion.ts
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { addCompleter, setCompleters } from 'ace-builds/src-noconflict/ext-language_tools';
|
||||||
|
import { getDatabaseInfo } from '../utility/metadataLoaders';
|
||||||
|
import analyseQuerySources from './analyseQuerySources';
|
||||||
|
|
||||||
|
const COMMON_KEYWORDS = [
|
||||||
|
'select',
|
||||||
|
'where',
|
||||||
|
'update',
|
||||||
|
'delete',
|
||||||
|
'group',
|
||||||
|
'order',
|
||||||
|
'from',
|
||||||
|
'by',
|
||||||
|
'create',
|
||||||
|
'table',
|
||||||
|
'drop',
|
||||||
|
'alter',
|
||||||
|
'view',
|
||||||
|
'execute',
|
||||||
|
'procedure',
|
||||||
|
'distinct',
|
||||||
|
'go',
|
||||||
|
];
|
||||||
|
|
||||||
|
export function mountCodeCompletion({ conid, database, editor }) {
|
||||||
|
setCompleters([]);
|
||||||
|
addCompleter({
|
||||||
|
getCompletions: async function (editor, session, pos, prefix, callback) {
|
||||||
|
const cursor = session.selection.cursor;
|
||||||
|
const line = session.getLine(cursor.row).slice(0, cursor.column);
|
||||||
|
const dbinfo = await getDatabaseInfo({ conid, database });
|
||||||
|
|
||||||
|
let list = COMMON_KEYWORDS.map(word => ({
|
||||||
|
name: word,
|
||||||
|
value: word,
|
||||||
|
caption: word,
|
||||||
|
meta: 'keyword',
|
||||||
|
score: 800,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (dbinfo) {
|
||||||
|
const colMatch = line.match(/([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]*)?$/);
|
||||||
|
if (colMatch) {
|
||||||
|
const table = colMatch[1];
|
||||||
|
const sources = analyseQuerySources(editor.getValue(), [
|
||||||
|
...dbinfo.tables.map(x => x.pureName),
|
||||||
|
...dbinfo.views.map(x => x.pureName),
|
||||||
|
]);
|
||||||
|
const source = sources.find(x => (x.alias || x.name) == table);
|
||||||
|
console.log('sources', sources);
|
||||||
|
console.log('table', table, source);
|
||||||
|
if (source) {
|
||||||
|
const table = dbinfo.tables.find(x => x.pureName == source.name);
|
||||||
|
if (table) {
|
||||||
|
list = [
|
||||||
|
...table.columns.map(x => ({
|
||||||
|
name: x.columnName,
|
||||||
|
value: x.columnName,
|
||||||
|
caption: x.columnName,
|
||||||
|
meta: 'column',
|
||||||
|
score: 1000,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
list = [
|
||||||
|
...list,
|
||||||
|
...dbinfo.tables.map(x => ({
|
||||||
|
name: x.pureName,
|
||||||
|
value: x.pureName,
|
||||||
|
caption: x.pureName,
|
||||||
|
meta: 'table',
|
||||||
|
score: 1000,
|
||||||
|
})),
|
||||||
|
...dbinfo.views.map(x => ({
|
||||||
|
name: x.pureName,
|
||||||
|
value: x.pureName,
|
||||||
|
caption: x.pureName,
|
||||||
|
meta: 'view',
|
||||||
|
score: 1000,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (/(join)|(from)|(update)|(delete)|(insert)\s*([a-zA-Z0-9_]*)?$/i.test(line)) {
|
||||||
|
// if (dbinfo) {
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
callback(null, list);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const doLiveAutocomplete = function (e) {
|
||||||
|
const editor = e.editor;
|
||||||
|
var hasCompleter = editor.completer && editor.completer.activated;
|
||||||
|
const session = editor.session;
|
||||||
|
const cursor = session.selection.cursor;
|
||||||
|
const line = session.getLine(cursor.row).slice(0, cursor.column);
|
||||||
|
|
||||||
|
// We don't want to autocomplete with no prefix
|
||||||
|
if (e.command.name === 'backspace') {
|
||||||
|
// do not hide after backspace
|
||||||
|
} else if (e.command.name === 'insertstring') {
|
||||||
|
if ((!hasCompleter && /^[a-zA-Z]/.test(e.args)) || e.args == '.') {
|
||||||
|
editor.execCommand('startAutocomplete');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.args == ' ' && /((from)|(join))\s*$/i.test(line)) {
|
||||||
|
editor.execCommand('startAutocomplete');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
editor.commands.on('afterExec', doLiveAutocomplete);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
editor.commands.removeListener('afterExec', doLiveAutocomplete);
|
||||||
|
};
|
||||||
|
}
|
@ -204,6 +204,8 @@
|
|||||||
<svelte:fragment slot="1">
|
<svelte:fragment slot="1">
|
||||||
<SqlEditor
|
<SqlEditor
|
||||||
engine={$connection && $connection.engine}
|
engine={$connection && $connection.engine}
|
||||||
|
{conid}
|
||||||
|
{database}
|
||||||
value={$editorState.value || ''}
|
value={$editorState.value || ''}
|
||||||
menu={createMenu()}
|
menu={createMenu()}
|
||||||
on:input={e => setEditorData(e.detail)}
|
on:input={e => setEditorData(e.detail)}
|
||||||
|
Loading…
Reference in New Issue
Block a user