Skip to content
This repository was archived by the owner on Mar 12, 2020. It is now read-only.

Commit 5b37c84

Browse files
authored
Merge pull request #85 from mtxr/smart_completions
Smart completions
2 parents c624d3e + e2e66e7 commit 5b37c84

6 files changed

Lines changed: 713 additions & 65 deletions

File tree

SQLTools.py

Lines changed: 79 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import sys
44
import os
5+
import re
56
from functools import partial
67

78
import sublime
@@ -13,6 +14,7 @@
1314
from .SQLToolsAPI.Storage import Storage, Settings
1415
from .SQLToolsAPI.Connection import Connection
1516
from .SQLToolsAPI.History import History
17+
from .SQLToolsAPI.Completion import Completion
1618

1719
SYNTAX_PLAIN_TEXT = 'Packages/Text/Plain text.tmLanguage'
1820
SYNTAX_SQL = 'Packages/SQL/SQL.tmLanguage'
@@ -265,56 +267,85 @@ def selectFunction(callback):
265267

266268
@staticmethod
267269
def on_query_completions(view, prefix, locations):
268-
completions = view.extract_completions(prefix)
269-
270-
if prefix == "":
271-
currentPoint = locations[0]
272-
lineStartPoint = view.line(currentPoint).begin()
273-
lineStartToLocation = sublime.Region(lineStartPoint, currentPoint)
274-
try:
275-
# everything after last space is a prefix
276-
prefix = view.substr(lineStartToLocation).split(" ").pop()
277-
except Exception:
278-
pass
270+
# skip completions, if no connection
271+
if ST.conn is None:
272+
return None
273+
274+
if not len(locations):
275+
return None
279276

280277
selectors = settings.get('selectors', [])
281-
if not selectors:
282-
return completions + ST.getAutoCompleteList(prefix)
283-
for selector in selectors:
284-
if view.match_selector(locations[0], selector):
285-
return completions + ST.getAutoCompleteList(prefix)
286-
return None
278+
selectorMatched = False
279+
if selectors:
280+
for selector in selectors:
281+
if view.match_selector(locations[0], selector):
282+
selectorMatched = True
283+
break
284+
285+
if not selectorMatched:
286+
return None
287+
288+
# completions enabled? if yes, determine which type
289+
completionType = settings.get('autocompletion', 'smart')
290+
if not completionType:
291+
return None # autocompletion disabled
292+
completionType = str(completionType).strip()
293+
if completionType not in ['basic', 'smart']:
294+
completionType = 'smart'
295+
296+
# no completions inside strings
297+
if view.match_selector(locations[0], 'string'):
298+
return None
299+
300+
# sublimePrefix = prefix
301+
# sublimeCompletions = view.extract_completions(sublimePrefix, locations[0])
302+
303+
# preferably get prefix ourselves instead of using default sublime "prefix".
304+
# Sublime will return only last portion of this preceding text. Given:
305+
# SELECT table.col|
306+
# sublime will return: "col", and we need: "table.col"
307+
# to know more precisely which completions are more appropriate
308+
309+
# get a Region that starts at the beginning of current line
310+
# and ends at current cursor position
311+
currentPoint = locations[0]
312+
lineStartPoint = view.line(currentPoint).begin()
313+
lineStartToLocation = sublime.Region(lineStartPoint, currentPoint)
314+
try:
315+
lineStr = view.substr(lineStartToLocation)
316+
prefix = re.split('[^\w.]+', lineStr).pop()
317+
except Exception as e:
318+
Log(e)
319+
pass
320+
321+
# determine desired keywords case from settings
322+
formatSettings = settings.get('format', {})
323+
keywordCase = formatSettings.get('keyword_case', 'upper')
324+
uppercaseKeywords = (keywordCase.lower() == 'upper')
325+
326+
inhibit = False
327+
completion = Completion(uppercaseKeywords, ST.tables, ST.columns, ST.functions)
328+
329+
if completionType == 'basic':
330+
ST.autoCompleteList = completion.getBasicAutoCompleteList(prefix)
331+
else:
332+
# use current paragraph as sql text to parse
333+
sqlRegion = expand_to_paragraph(view, currentPoint)
334+
sql = view.substr(sqlRegion)
335+
sqlToCursorRegion = sublime.Region(sqlRegion.begin(), currentPoint)
336+
sqlToCursor = view.substr(sqlToCursorRegion)
337+
ST.autoCompleteList, inhibit = completion.getAutoCompleteList(prefix, sql, sqlToCursor)
338+
339+
# safe check here, so even if we return empty completions and inhibit is true
340+
# we return empty completions to show default sublime completions
341+
if ST.autoCompleteList is None or len(ST.autoCompleteList) == 0:
342+
return None
343+
344+
if inhibit:
345+
return (ST.autoCompleteList, sublime.INHIBIT_WORD_COMPLETIONS)
346+
347+
return ST.autoCompleteList
287348

288-
@staticmethod
289-
def getAutoCompleteList(word):
290-
ST.autoCompleteList = []
291-
for w in ST.tables:
292-
try:
293-
if word.lower() in w.lower():
294-
ST.autoCompleteList.append(
295-
("{0}\t({1})".format(w, 'Table'), w))
296-
except UnicodeDecodeError:
297-
continue
298-
299-
for w in ST.columns:
300-
try:
301-
if word.lower() in w.lower():
302-
w = w.split(".")
303-
ST.autoCompleteList.append(("{0}\t({1})".format(
304-
w[1], w[0] + ' Col'), w[1]))
305-
except Exception:
306-
continue
307-
308-
for w in ST.functions:
309-
try:
310-
if word.lower() in w.lower():
311-
ST.autoCompleteList.append(
312-
("{0}\t({1})".format(w, 'Func'), w))
313-
except Exception:
314-
continue
315-
316-
ST.autoCompleteList.sort()
317-
return (ST.autoCompleteList)
318349

319350
# #
320351
# # Commands
@@ -533,6 +564,7 @@ def reload():
533564
import imp
534565
imp.reload(sys.modules[__package__ + ".SQLToolsAPI"])
535566
imp.reload(sys.modules[__package__ + ".SQLToolsAPI.Utils"])
567+
imp.reload(sys.modules[__package__ + ".SQLToolsAPI.Completion"])
536568
imp.reload(sys.modules[__package__ + ".SQLToolsAPI.Storage"])
537569
imp.reload(sys.modules[__package__ + ".SQLToolsAPI.History"])
538570
imp.reload(sys.modules[__package__ + ".SQLToolsAPI.Log"])

SQLTools.sublime-settings

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* An empty list means autocompletion always active.
1313
*/
1414
"selectors": ["source.sql", "source.pgsql", "source.plpgsql.postgres", "source.plsql.oracle", "source.tsql"],
15+
"autocompletion": "smart", /* possible values: "basic", "smart" or false (disable) */
1516
"unescape_quotes" : [
1617
"php"
1718
],
@@ -54,8 +55,8 @@
5455
"args": "-h {host} -p {port} -U {username} -d {database}",
5556
"queries": {
5657
"desc" : {
57-
"query": "SELECT '|' || CASE WHEN n.nspname = current_schema() THEN quote_ident(c.relname) ELSE quote_ident(n.nspname)||'.'||quote_ident(c.relname) END ||'|' AS tblname FROM pg_catalog.pg_class AS c INNER JOIN pg_catalog.pg_namespace AS n ON n.oid = c.relnamespace WHERE relkind = 'r' AND n.nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema') ORDER BY n.nspname = current_schema() DESC, pg_catalog.pg_table_is_visible(c.oid) DESC, n.nspname, c.relname",
58-
"options": ["-t", "--no-psqlrc"],
58+
"query": "select '|' || quote_ident(table_schema)||'.'||quote_ident(table_name) ||'|' as tblname from information_schema.tables where table_schema = any(current_schemas(false)) and table_schema not in ('pg_catalog', 'information_schema') order by table_schema = current_schema() desc, table_schema, table_name",
59+
"options": ["--tuples-only", "--no-psqlrc"],
5960
"format" : "|%s|"
6061
},
6162
"desc table": {
@@ -69,13 +70,13 @@
6970
"format" : "|%s|"
7071
},
7172
"columns": {
72-
"query": "SELECT DISTINCT '|' || quote_ident(table_name) || '.' || quote_ident(column_name) || '|' FROM information_schema.columns WHERE table_schema = CURRENT_SCHEMA() GROUP BY table_name, column_name;",
73-
"options": ["-t", "--no-psqlrc"],
73+
"query": "select '|' || quote_ident(table_name) || '.' || quote_ident(column_name) || '|' from information_schema.columns where table_schema = any(current_schemas(false)) and table_schema not in ('pg_catalog', 'information_schema') order by table_name, ordinal_position",
74+
"options": ["--tuples-only", "--no-psqlrc"],
7475
"format" : "|%s|"
7576
},
7677
"functions": {
77-
"query": "SELECT '|' || CASE WHEN n.nspname = current_schema() THEN quote_ident(f.proname) ELSE quote_ident(n.nspname)||'.'||quote_ident(f.proname) END || '(' || pg_get_function_identity_arguments(f.oid) || ')' || '|' AS funname FROM pg_catalog.pg_proc AS f INNER JOIN pg_catalog.pg_namespace AS n ON n.oid = f.pronamespace WHERE proisagg = false AND n.nspname NOT IN ('pg_catalog', 'information_schema')",
78-
"options": ["-t", "--no-psqlrc"],
78+
"query": "select '|' || quote_ident(n.nspname)||'.'||quote_ident(f.proname) || '(' || pg_get_function_identity_arguments(f.oid) || ')' || '|' as funname from pg_catalog.pg_proc as f inner join pg_catalog.pg_namespace as n on n.oid = f.pronamespace where f.proisagg = false and n.nspname = any(current_schemas(false)) and n.nspname not in ('pg_catalog', 'information_schema')",
79+
"options": ["--tuples-only", "--no-psqlrc"],
7980
"format" : "|%s|"
8081
},
8182
"desc function": {
@@ -116,7 +117,12 @@
116117
"args": "{username}/{password}@\"(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST={host})(PORT={port})))(CONNECT_DATA=(SERVICE_NAME={service})))\"",
117118
"queries": {
118119
"desc" : {
119-
"query": "select CONCAT(CONCAT('| ', table_name), ' |') as tables from user_tables;",
120+
"query": "select concat(concat(concat(concat('|', owner), '.'), table_name), '|') as tbls from all_tables where owner = sys_context('USERENV', 'CURRENT_SCHEMA');",
121+
"options": ["-S"],
122+
"format" : "|%s|"
123+
},
124+
"columns": {
125+
"query": "select concat(concat(concat(concat('|', c.table_name), '.'), c.column_name), '|') as cols from all_tab_columns c inner join all_tables t ON c.owner = t.owner and c.table_name = t.table_name where c.owner = sys_context('USERENV', 'CURRENT_SCHEMA');",
120126
"options": ["-S"],
121127
"format" : "|%s|"
122128
},
@@ -143,7 +149,7 @@
143149
"args": "-h{host} -P{port} -u\"{username}\" -p\"{password}\" -D\"{database}\"",
144150
"queries": {
145151
"desc" : {
146-
"query": "show tables",
152+
"query": "select concat(table_schema, '.', table_name) from information_schema.tables where table_schema = database() order by table_name;",
147153
"options": ["-f", "--table", "--skip-column-names"],
148154
"format" : "|%s|"
149155
},
@@ -158,8 +164,13 @@
158164
"format" : "|%s|"
159165
},
160166
"columns": {
161-
"query": "SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = DATABASE();",
162-
"options": ["-f", "--table"],
167+
"query": "select concat(table_name, '.', column_name) from information_schema.columns where table_schema = database() order by table_name, ordinal_position;",
168+
"options": ["-f", "--table", "--skip-column-names"],
169+
"format" : "|%s|"
170+
},
171+
"functions": {
172+
"query": "select concat(routine_schema, '.', routine_name) from information_schema.routines where routine_schema = database();",
173+
"options": ["-f", "--table", "--skip-column-names"],
163174
"format" : "|%s|"
164175
},
165176
"explain plan": {
@@ -175,8 +186,13 @@
175186
"args": "-h {host} -p {port} -U \"{username}\" -w \"{password}\" -d \"{database}\"",
176187
"queries": {
177188
"desc" : {
178-
"query": "\\dt",
179-
"options": ["-t"],
189+
"query": "select '|' || table_schema || '.' || table_name || '|' as tblname from v_catalog.tables where is_system_table = false",
190+
"options": ["--tuples-only", "--no-vsqlrc"],
191+
"format" : "|%s|"
192+
},
193+
"columns": {
194+
"query": "select '|' || table_name || '.' || column_name || '|' as tblname from v_catalog.columns where is_system_table = false order by table_name, ordinal_position",
195+
"options": ["--tuples-only", "--no-vsqlrc"],
180196
"format" : "|%s|"
181197
},
182198
"desc table": {
@@ -202,7 +218,13 @@
202218
"args": "-S {host}:{port} -U\"{username}\" -P\"{password}\" -D{database}",
203219
"queries": {
204220
"desc": {
205-
"query": "select table_name from information_schema.tables order by 1;",
221+
"query": "select concat(table_schema, '.', table_name) from information_schema.tables order by table_name;",
222+
"before" :["\\set semicolon_cmd=\"\\go -mpretty -l -h -f\""],
223+
"options": [],
224+
"format": "|%s|"
225+
},
226+
"columns": {
227+
"query": "select concat(table_name, '.', column_name) from information_schema.columns order by table_name, ordinal_position;",
206228
"before" :["\\set semicolon_cmd=\"\\go -mpretty -l -h -f\""],
207229
"options": [],
208230
"format": "|%s|"

0 commit comments

Comments
 (0)