mirror of
https://github.com/dbgate/dbgate
synced 2024-11-07 20:26:23 +00:00
code completion
This commit is contained in:
parent
b7b9dde5ae
commit
d4989c75ca
@ -9,11 +9,20 @@
|
||||
<script lang="ts">
|
||||
import AceEditor from './AceEditor.svelte';
|
||||
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 conid;
|
||||
export let database;
|
||||
export let readOnly;
|
||||
|
||||
let domEditor;
|
||||
|
||||
let mode;
|
||||
|
||||
const tabVisible: any = getContext('tabVisible');
|
||||
|
||||
$: {
|
||||
const match = (engine || '').match(/^([^@]*)@/);
|
||||
mode = engineToMode[match ? match[1] : engine] || 'sql';
|
||||
@ -22,6 +31,25 @@
|
||||
export function getEditor(): ace.Editor {
|
||||
return domEditor.getEditor();
|
||||
}
|
||||
|
||||
$: effect = useEffect(() => {
|
||||
const editor = domEditor?.getEditor();
|
||||
if ($tabVisible && conid && database && !readOnly && editor) {
|
||||
return mountCodeCompletion({ conid, database, editor });
|
||||
}
|
||||
return () => {};
|
||||
});
|
||||
$: $effect;
|
||||
</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">
|
||||
<SqlEditor
|
||||
engine={$connection && $connection.engine}
|
||||
{conid}
|
||||
{database}
|
||||
value={$editorState.value || ''}
|
||||
menu={createMenu()}
|
||||
on:input={e => setEditorData(e.detail)}
|
||||
|
Loading…
Reference in New Issue
Block a user