mycli 1.29.1__py3-none-any.whl
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.
- doc/key_bindings.rst +65 -0
- mycli/AUTHORS +107 -0
- mycli/SPONSORS +31 -0
- mycli/__init__.py +3 -0
- mycli/clibuffer.py +55 -0
- mycli/clistyle.py +145 -0
- mycli/clitoolbar.py +52 -0
- mycli/compat.py +6 -0
- mycli/completion_refresher.py +153 -0
- mycli/config.py +334 -0
- mycli/key_bindings.py +135 -0
- mycli/lexer.py +11 -0
- mycli/magic.py +63 -0
- mycli/main.py +1468 -0
- mycli/myclirc +159 -0
- mycli/packages/__init__.py +0 -0
- mycli/packages/completion_engine.py +293 -0
- mycli/packages/filepaths.py +106 -0
- mycli/packages/paramiko_stub/__init__.py +31 -0
- mycli/packages/parseutils.py +263 -0
- mycli/packages/prompt_utils.py +53 -0
- mycli/packages/special/__init__.py +12 -0
- mycli/packages/special/dbcommands.py +154 -0
- mycli/packages/special/delimitercommand.py +77 -0
- mycli/packages/special/favoritequeries.py +62 -0
- mycli/packages/special/iocommands.py +540 -0
- mycli/packages/special/main.py +119 -0
- mycli/packages/special/utils.py +48 -0
- mycli/packages/tabular_output/__init__.py +0 -0
- mycli/packages/tabular_output/sql_format.py +63 -0
- mycli/packages/toolkit/__init__.py +0 -0
- mycli/packages/toolkit/fzf.py +45 -0
- mycli/packages/toolkit/history.py +52 -0
- mycli/sqlcompleter.py +1261 -0
- mycli/sqlexecute.py +449 -0
- mycli-1.29.1.dist-info/AUTHORS.rst +3 -0
- mycli-1.29.1.dist-info/LICENSE.txt +34 -0
- mycli-1.29.1.dist-info/METADATA +254 -0
- mycli-1.29.1.dist-info/RECORD +88 -0
- mycli-1.29.1.dist-info/WHEEL +5 -0
- mycli-1.29.1.dist-info/entry_points.txt +2 -0
- mycli-1.29.1.dist-info/top_level.txt +3 -0
- test/__init__.py +0 -0
- test/conftest.py +38 -0
- test/features/__init__.py +0 -0
- test/features/auto_vertical.feature +12 -0
- test/features/basic_commands.feature +19 -0
- test/features/connection.feature +35 -0
- test/features/crud_database.feature +30 -0
- test/features/crud_table.feature +49 -0
- test/features/db_utils.py +74 -0
- test/features/environment.py +145 -0
- test/features/fixture_data/help.txt +24 -0
- test/features/fixture_data/help_commands.txt +31 -0
- test/features/fixture_utils.py +28 -0
- test/features/iocommands.feature +47 -0
- test/features/named_queries.feature +24 -0
- test/features/specials.feature +7 -0
- test/features/steps/__init__.py +0 -0
- test/features/steps/auto_vertical.py +47 -0
- test/features/steps/basic_commands.py +98 -0
- test/features/steps/connection.py +53 -0
- test/features/steps/crud_database.py +109 -0
- test/features/steps/crud_table.py +118 -0
- test/features/steps/iocommands.py +97 -0
- test/features/steps/named_queries.py +87 -0
- test/features/steps/specials.py +27 -0
- test/features/steps/utils.py +12 -0
- test/features/steps/wrappers.py +105 -0
- test/features/wrappager.py +16 -0
- test/myclirc +161 -0
- test/mylogin.cnf +0 -0
- test/test.txt +1 -0
- test/test_clistyle.py +28 -0
- test/test_completion_engine.py +595 -0
- test/test_completion_refresher.py +88 -0
- test/test_config.py +202 -0
- test/test_dbspecial.py +35 -0
- test/test_main.py +555 -0
- test/test_naive_completion.py +53 -0
- test/test_parseutils.py +174 -0
- test/test_plan.wiki +38 -0
- test/test_prompt_utils.py +11 -0
- test/test_smart_completion_public_schema_only.py +397 -0
- test/test_special_iocommands.py +313 -0
- test/test_sqlexecute.py +288 -0
- test/test_tabular_output.py +108 -0
- test/utils.py +87 -0
doc/key_bindings.rst
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
*************
|
|
2
|
+
Key Bindings:
|
|
3
|
+
*************
|
|
4
|
+
|
|
5
|
+
Most key bindings are simply inherited from `prompt-toolkit <https://python-prompt-toolkit.readthedocs.io/en/master/index.html>`_ .
|
|
6
|
+
|
|
7
|
+
The following key bindings are special to mycli:
|
|
8
|
+
|
|
9
|
+
###
|
|
10
|
+
F2
|
|
11
|
+
###
|
|
12
|
+
|
|
13
|
+
Enable/Disable SmartCompletion Mode.
|
|
14
|
+
|
|
15
|
+
###
|
|
16
|
+
F3
|
|
17
|
+
###
|
|
18
|
+
|
|
19
|
+
Enable/Disable Multiline Mode.
|
|
20
|
+
|
|
21
|
+
###
|
|
22
|
+
F4
|
|
23
|
+
###
|
|
24
|
+
|
|
25
|
+
Toggle between Vi and Emacs mode.
|
|
26
|
+
|
|
27
|
+
###
|
|
28
|
+
Tab
|
|
29
|
+
###
|
|
30
|
+
|
|
31
|
+
Force autocompletion at cursor.
|
|
32
|
+
|
|
33
|
+
#######
|
|
34
|
+
C-space
|
|
35
|
+
#######
|
|
36
|
+
|
|
37
|
+
Initialize autocompletion at cursor.
|
|
38
|
+
|
|
39
|
+
If the autocompletion menu is not showing, display it with the appropriate completions for the context.
|
|
40
|
+
|
|
41
|
+
If the menu is showing, select the next completion.
|
|
42
|
+
|
|
43
|
+
#########
|
|
44
|
+
ESC Enter
|
|
45
|
+
#########
|
|
46
|
+
|
|
47
|
+
Introduce a line break in multi-line mode, or dispatch the command in single-line mode.
|
|
48
|
+
|
|
49
|
+
The sequence ESC-Enter is often sent by Alt-Enter.
|
|
50
|
+
|
|
51
|
+
##################
|
|
52
|
+
C-x p (Emacs-mode)
|
|
53
|
+
##################
|
|
54
|
+
|
|
55
|
+
Prettify and indent current statement, usually into multiple lines.
|
|
56
|
+
|
|
57
|
+
Only accepts buffers containing single SQL statements.
|
|
58
|
+
|
|
59
|
+
##################
|
|
60
|
+
C-x u (Emacs-mode)
|
|
61
|
+
##################
|
|
62
|
+
|
|
63
|
+
Unprettify and dedent current statement, usually into one line.
|
|
64
|
+
|
|
65
|
+
Only accepts buffers containing single SQL statements.
|
mycli/AUTHORS
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Core Developers:
|
|
2
|
+
----------------
|
|
3
|
+
|
|
4
|
+
* Thomas Roten
|
|
5
|
+
* Irina Truong
|
|
6
|
+
* Matheus Rosa
|
|
7
|
+
* Darik Gamble
|
|
8
|
+
* Dick Marinus
|
|
9
|
+
* Amjith Ramanujam
|
|
10
|
+
|
|
11
|
+
Contributors:
|
|
12
|
+
-------------
|
|
13
|
+
|
|
14
|
+
* 0xflotus
|
|
15
|
+
* Abirami P
|
|
16
|
+
* Adam Chainz
|
|
17
|
+
* Aljosha Papsch
|
|
18
|
+
* Andy Teijelo Pérez
|
|
19
|
+
* Angelo Lupo
|
|
20
|
+
* Artem Bezsmertnyi
|
|
21
|
+
* bitkeen
|
|
22
|
+
* bjarnagin
|
|
23
|
+
* BuonOmo
|
|
24
|
+
* caitinggui
|
|
25
|
+
* Carlos Afonso
|
|
26
|
+
* Casper Langemeijer
|
|
27
|
+
* chainkite
|
|
28
|
+
* Claude Becker
|
|
29
|
+
* Colin Caine
|
|
30
|
+
* cxbig
|
|
31
|
+
* Daniel Black
|
|
32
|
+
* Daniel West
|
|
33
|
+
* Daniël van Eeden
|
|
34
|
+
* Fabrizio Gennari
|
|
35
|
+
* François Pietka
|
|
36
|
+
* Frederic Aoustin
|
|
37
|
+
* Georgy Frolov
|
|
38
|
+
* Heath Naylor
|
|
39
|
+
* Huachao Mao
|
|
40
|
+
* Ishaan Bhimwal
|
|
41
|
+
* Jakub Boukal
|
|
42
|
+
* jbruno
|
|
43
|
+
* Jerome Provensal
|
|
44
|
+
* Jialong Liu
|
|
45
|
+
* Johannes Hoff
|
|
46
|
+
* John Sterling
|
|
47
|
+
* Jonathan Bruno
|
|
48
|
+
* Jonathan Lloyd
|
|
49
|
+
* Jonathan Slenders
|
|
50
|
+
* Kacper Kwapisz
|
|
51
|
+
* Karthikeyan Singaravelan
|
|
52
|
+
* kevinhwang91
|
|
53
|
+
* KITAGAWA Yasutaka
|
|
54
|
+
* Klaus Wünschel
|
|
55
|
+
* laixintao
|
|
56
|
+
* Lennart Weller
|
|
57
|
+
* Martijn Engler
|
|
58
|
+
* Massimiliano Torromeo
|
|
59
|
+
* Michał Górny
|
|
60
|
+
* Mike Palandra
|
|
61
|
+
* Mikhail Borisov
|
|
62
|
+
* Morgan Mitchell
|
|
63
|
+
* mrdeathless
|
|
64
|
+
* Nathan Huang
|
|
65
|
+
* Nicolas Palumbo
|
|
66
|
+
* Phil Cohen
|
|
67
|
+
* QiaoHou Peng
|
|
68
|
+
* Roland Walker
|
|
69
|
+
* Ryan Smith
|
|
70
|
+
* Scrappy Soft
|
|
71
|
+
* Seamile
|
|
72
|
+
* Shoma Suzuki
|
|
73
|
+
* spacewander
|
|
74
|
+
* Steve Robbins
|
|
75
|
+
* Takeshi D. Itoh
|
|
76
|
+
* Terje Røsten
|
|
77
|
+
* Terseus
|
|
78
|
+
* Tyler Kuipers
|
|
79
|
+
* ushuz
|
|
80
|
+
* William GARCIA
|
|
81
|
+
* xeron
|
|
82
|
+
* Yang Zou
|
|
83
|
+
* Yasuhiro Matsumoto
|
|
84
|
+
* Yuanchun Shang
|
|
85
|
+
* Zach DeCook
|
|
86
|
+
* Zane C. Bowers-Hadley
|
|
87
|
+
* zer09
|
|
88
|
+
* Zhaolong Zhu
|
|
89
|
+
* Zhidong
|
|
90
|
+
* Zhongyang Guan
|
|
91
|
+
* Arvind Mishra
|
|
92
|
+
* Kevin Schmeichel
|
|
93
|
+
* Mel Dafert
|
|
94
|
+
* Thomas Copper
|
|
95
|
+
* Will Wang
|
|
96
|
+
* Alfred Wingate
|
|
97
|
+
* Zhanze Wang
|
|
98
|
+
* Houston Wong
|
|
99
|
+
* Mohamed Rezk
|
|
100
|
+
* Ryosuke Kazami
|
|
101
|
+
* Cornel Cruceru
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
Created by:
|
|
105
|
+
-----------
|
|
106
|
+
|
|
107
|
+
Amjith Ramanujam
|
mycli/SPONSORS
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Many thanks to the following Kickstarter backers.
|
|
2
|
+
|
|
3
|
+
* Tech Blue Software
|
|
4
|
+
* jweiland.net
|
|
5
|
+
|
|
6
|
+
# Silver Sponsors
|
|
7
|
+
|
|
8
|
+
* Whitane Tech
|
|
9
|
+
* Open Query Pty Ltd
|
|
10
|
+
* Prathap Ramamurthy
|
|
11
|
+
* Lincoln Loop
|
|
12
|
+
|
|
13
|
+
# Sponsors
|
|
14
|
+
|
|
15
|
+
* Nathan Taggart
|
|
16
|
+
* Iryna Cherniavska
|
|
17
|
+
* Sudaraka Wijesinghe
|
|
18
|
+
* www.mysqlfanboy.com
|
|
19
|
+
* Steve Robbins
|
|
20
|
+
* Norbert Spichtig
|
|
21
|
+
* orpharion bestheneme
|
|
22
|
+
* Daniel Black
|
|
23
|
+
* Anonymous
|
|
24
|
+
* Magnus udd
|
|
25
|
+
* Anonymous
|
|
26
|
+
* Lewis Peckover
|
|
27
|
+
* Cyrille Tabary
|
|
28
|
+
* Heath Naylor
|
|
29
|
+
* Ted Pennings
|
|
30
|
+
* Chris Anderton
|
|
31
|
+
* Jonathan Slenders
|
mycli/__init__.py
ADDED
mycli/clibuffer.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from prompt_toolkit.enums import DEFAULT_BUFFER
|
|
2
|
+
from prompt_toolkit.filters import Condition
|
|
3
|
+
from prompt_toolkit.application import get_app
|
|
4
|
+
from .packages import special
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def cli_is_multiline(mycli):
|
|
8
|
+
@Condition
|
|
9
|
+
def cond():
|
|
10
|
+
doc = get_app().layout.get_buffer_by_name(DEFAULT_BUFFER).document
|
|
11
|
+
|
|
12
|
+
if not mycli.multi_line:
|
|
13
|
+
return False
|
|
14
|
+
else:
|
|
15
|
+
return not _multiline_exception(doc.text)
|
|
16
|
+
|
|
17
|
+
return cond
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _multiline_exception(text):
|
|
21
|
+
orig = text
|
|
22
|
+
text = text.strip()
|
|
23
|
+
|
|
24
|
+
# Multi-statement favorite query is a special case. Because there will
|
|
25
|
+
# be a semicolon separating statements, we can't consider semicolon an
|
|
26
|
+
# EOL. Let's consider an empty line an EOL instead.
|
|
27
|
+
if text.startswith("\\fs"):
|
|
28
|
+
return orig.endswith("\n")
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
# Special Command
|
|
32
|
+
text.startswith("\\")
|
|
33
|
+
or
|
|
34
|
+
# Delimiter declaration
|
|
35
|
+
text.lower().startswith("delimiter")
|
|
36
|
+
or
|
|
37
|
+
# Ended with the current delimiter (usually a semi-column)
|
|
38
|
+
text.endswith(special.get_current_delimiter())
|
|
39
|
+
or text.endswith("\\g")
|
|
40
|
+
or text.endswith("\\G")
|
|
41
|
+
or text.endswith(r"\e")
|
|
42
|
+
or text.endswith(r"\clip")
|
|
43
|
+
or
|
|
44
|
+
# Exit doesn't need semi-column`
|
|
45
|
+
(text == "exit")
|
|
46
|
+
or
|
|
47
|
+
# Quit doesn't need semi-column
|
|
48
|
+
(text == "quit")
|
|
49
|
+
or
|
|
50
|
+
# To all teh vim fans out there
|
|
51
|
+
(text == ":q")
|
|
52
|
+
or
|
|
53
|
+
# just a plain enter without any text
|
|
54
|
+
(text == "")
|
|
55
|
+
)
|
mycli/clistyle.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import pygments.styles
|
|
4
|
+
from pygments.token import string_to_tokentype, Token
|
|
5
|
+
from pygments.style import Style as PygmentsStyle
|
|
6
|
+
from pygments.util import ClassNotFound
|
|
7
|
+
from prompt_toolkit.styles.pygments import style_from_pygments_cls
|
|
8
|
+
from prompt_toolkit.styles import merge_styles, Style
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
# map Pygments tokens (ptk 1.0) to class names (ptk 2.0).
|
|
13
|
+
TOKEN_TO_PROMPT_STYLE = {
|
|
14
|
+
Token.Menu.Completions.Completion.Current: "completion-menu.completion.current",
|
|
15
|
+
Token.Menu.Completions.Completion: "completion-menu.completion",
|
|
16
|
+
Token.Menu.Completions.Meta.Current: "completion-menu.meta.completion.current",
|
|
17
|
+
Token.Menu.Completions.Meta: "completion-menu.meta.completion",
|
|
18
|
+
Token.Menu.Completions.MultiColumnMeta: "completion-menu.multi-column-meta",
|
|
19
|
+
Token.Menu.Completions.ProgressButton: "scrollbar.arrow", # best guess
|
|
20
|
+
Token.Menu.Completions.ProgressBar: "scrollbar", # best guess
|
|
21
|
+
Token.SelectedText: "selected",
|
|
22
|
+
Token.SearchMatch: "search",
|
|
23
|
+
Token.SearchMatch.Current: "search.current",
|
|
24
|
+
Token.Toolbar: "bottom-toolbar",
|
|
25
|
+
Token.Toolbar.Off: "bottom-toolbar.off",
|
|
26
|
+
Token.Toolbar.On: "bottom-toolbar.on",
|
|
27
|
+
Token.Toolbar.Search: "search-toolbar",
|
|
28
|
+
Token.Toolbar.Search.Text: "search-toolbar.text",
|
|
29
|
+
Token.Toolbar.System: "system-toolbar",
|
|
30
|
+
Token.Toolbar.Arg: "arg-toolbar",
|
|
31
|
+
Token.Toolbar.Arg.Text: "arg-toolbar.text",
|
|
32
|
+
Token.Toolbar.Transaction.Valid: "bottom-toolbar.transaction.valid",
|
|
33
|
+
Token.Toolbar.Transaction.Failed: "bottom-toolbar.transaction.failed",
|
|
34
|
+
Token.Output.Header: "output.header",
|
|
35
|
+
Token.Output.OddRow: "output.odd-row",
|
|
36
|
+
Token.Output.EvenRow: "output.even-row",
|
|
37
|
+
Token.Output.Null: "output.null",
|
|
38
|
+
Token.Prompt: "prompt",
|
|
39
|
+
Token.Continuation: "continuation",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# reverse dict for cli_helpers, because they still expect Pygments tokens.
|
|
43
|
+
PROMPT_STYLE_TO_TOKEN = {v: k for k, v in TOKEN_TO_PROMPT_STYLE.items()}
|
|
44
|
+
|
|
45
|
+
# all tokens that the Pygments MySQL lexer can produce
|
|
46
|
+
OVERRIDE_STYLE_TO_TOKEN = {
|
|
47
|
+
"sql.comment": Token.Comment,
|
|
48
|
+
"sql.comment.multi-line": Token.Comment.Multiline,
|
|
49
|
+
"sql.comment.single-line": Token.Comment.Single,
|
|
50
|
+
"sql.comment.optimizer-hint": Token.Comment.Special,
|
|
51
|
+
"sql.escape": Token.Error,
|
|
52
|
+
"sql.keyword": Token.Keyword,
|
|
53
|
+
"sql.datatype": Token.Keyword.Type,
|
|
54
|
+
"sql.literal": Token.Literal,
|
|
55
|
+
"sql.literal.date": Token.Literal.Date,
|
|
56
|
+
"sql.symbol": Token.Name,
|
|
57
|
+
"sql.quoted-schema-object": Token.Name.Quoted,
|
|
58
|
+
"sql.quoted-schema-object.escape": Token.Name.Quoted.Escape,
|
|
59
|
+
"sql.constant": Token.Name.Constant,
|
|
60
|
+
"sql.function": Token.Name.Function,
|
|
61
|
+
"sql.variable": Token.Name.Variable,
|
|
62
|
+
"sql.number": Token.Number,
|
|
63
|
+
"sql.number.binary": Token.Number.Bin,
|
|
64
|
+
"sql.number.float": Token.Number.Float,
|
|
65
|
+
"sql.number.hex": Token.Number.Hex,
|
|
66
|
+
"sql.number.integer": Token.Number.Integer,
|
|
67
|
+
"sql.operator": Token.Operator,
|
|
68
|
+
"sql.punctuation": Token.Punctuation,
|
|
69
|
+
"sql.string": Token.String,
|
|
70
|
+
"sql.string.double-quouted": Token.String.Double,
|
|
71
|
+
"sql.string.escape": Token.String.Escape,
|
|
72
|
+
"sql.string.single-quoted": Token.String.Single,
|
|
73
|
+
"sql.whitespace": Token.Text,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def parse_pygments_style(token_name, style_object, style_dict):
|
|
78
|
+
"""Parse token type and style string.
|
|
79
|
+
|
|
80
|
+
:param token_name: str name of Pygments token. Example: "Token.String"
|
|
81
|
+
:param style_object: pygments.style.Style instance to use as base
|
|
82
|
+
:param style_dict: dict of token names and their styles, customized to this cli
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
token_type = string_to_tokentype(token_name)
|
|
86
|
+
try:
|
|
87
|
+
other_token_type = string_to_tokentype(style_dict[token_name])
|
|
88
|
+
return token_type, style_object.styles[other_token_type]
|
|
89
|
+
except AttributeError:
|
|
90
|
+
return token_type, style_dict[token_name]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def style_factory(name, cli_style):
|
|
94
|
+
try:
|
|
95
|
+
style = pygments.styles.get_style_by_name(name)
|
|
96
|
+
except ClassNotFound:
|
|
97
|
+
style = pygments.styles.get_style_by_name("native")
|
|
98
|
+
|
|
99
|
+
prompt_styles = []
|
|
100
|
+
# prompt-toolkit used pygments tokens for styling before, switched to style
|
|
101
|
+
# names in 2.0. Convert old token types to new style names, for backwards compatibility.
|
|
102
|
+
for token in cli_style:
|
|
103
|
+
if token.startswith("Token."):
|
|
104
|
+
# treat as pygments token (1.0)
|
|
105
|
+
token_type, style_value = parse_pygments_style(token, style, cli_style)
|
|
106
|
+
if token_type in TOKEN_TO_PROMPT_STYLE:
|
|
107
|
+
prompt_style = TOKEN_TO_PROMPT_STYLE[token_type]
|
|
108
|
+
prompt_styles.append((prompt_style, style_value))
|
|
109
|
+
else:
|
|
110
|
+
# we don't want to support tokens anymore
|
|
111
|
+
logger.error("Unhandled style / class name: %s", token)
|
|
112
|
+
else:
|
|
113
|
+
# treat as prompt style name (2.0). See default style names here:
|
|
114
|
+
# https://github.com/jonathanslenders/python-prompt-toolkit/blob/master/prompt_toolkit/styles/defaults.py
|
|
115
|
+
prompt_styles.append((token, cli_style[token]))
|
|
116
|
+
|
|
117
|
+
override_style = Style([("bottom-toolbar", "noreverse")])
|
|
118
|
+
return merge_styles([style_from_pygments_cls(style), override_style, Style(prompt_styles)])
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def style_factory_output(name, cli_style):
|
|
122
|
+
try:
|
|
123
|
+
style = pygments.styles.get_style_by_name(name).styles
|
|
124
|
+
except ClassNotFound:
|
|
125
|
+
style = pygments.styles.get_style_by_name("native").styles
|
|
126
|
+
|
|
127
|
+
for token in cli_style:
|
|
128
|
+
if token.startswith("Token."):
|
|
129
|
+
token_type, style_value = parse_pygments_style(token, style, cli_style)
|
|
130
|
+
style.update({token_type: style_value})
|
|
131
|
+
elif token in PROMPT_STYLE_TO_TOKEN:
|
|
132
|
+
token_type = PROMPT_STYLE_TO_TOKEN[token]
|
|
133
|
+
style.update({token_type: cli_style[token]})
|
|
134
|
+
elif token in OVERRIDE_STYLE_TO_TOKEN:
|
|
135
|
+
token_type = OVERRIDE_STYLE_TO_TOKEN[token]
|
|
136
|
+
style.update({token_type: cli_style[token]})
|
|
137
|
+
else:
|
|
138
|
+
# TODO: cli helpers will have to switch to ptk.Style
|
|
139
|
+
logger.error("Unhandled style / class name: %s", token)
|
|
140
|
+
|
|
141
|
+
class OutputStyle(PygmentsStyle):
|
|
142
|
+
default_style = ""
|
|
143
|
+
styles = style
|
|
144
|
+
|
|
145
|
+
return OutputStyle
|
mycli/clitoolbar.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from prompt_toolkit.key_binding.vi_state import InputMode
|
|
2
|
+
from prompt_toolkit.application import get_app
|
|
3
|
+
from prompt_toolkit.enums import EditingMode
|
|
4
|
+
from .packages import special
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def create_toolbar_tokens_func(mycli, show_fish_help):
|
|
8
|
+
"""Return a function that generates the toolbar tokens."""
|
|
9
|
+
|
|
10
|
+
def get_toolbar_tokens():
|
|
11
|
+
result = [("class:bottom-toolbar", " ")]
|
|
12
|
+
|
|
13
|
+
if mycli.multi_line:
|
|
14
|
+
delimiter = special.get_current_delimiter()
|
|
15
|
+
result.append(
|
|
16
|
+
(
|
|
17
|
+
"class:bottom-toolbar",
|
|
18
|
+
" ({} [{}] will end the line) ".format("Semi-colon" if delimiter == ";" else "Delimiter", delimiter),
|
|
19
|
+
)
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
if mycli.multi_line:
|
|
23
|
+
result.append(("class:bottom-toolbar.on", "[F3] Multiline: ON "))
|
|
24
|
+
else:
|
|
25
|
+
result.append(("class:bottom-toolbar.off", "[F3] Multiline: OFF "))
|
|
26
|
+
if mycli.prompt_app.editing_mode == EditingMode.VI:
|
|
27
|
+
result.append(("class:bottom-toolbar.on", "Vi-mode ({})".format(_get_vi_mode())))
|
|
28
|
+
|
|
29
|
+
if mycli.toolbar_error_message:
|
|
30
|
+
result.append(("class:bottom-toolbar", " " + mycli.toolbar_error_message))
|
|
31
|
+
mycli.toolbar_error_message = None
|
|
32
|
+
|
|
33
|
+
if show_fish_help():
|
|
34
|
+
result.append(("class:bottom-toolbar", " Right-arrow to complete suggestion"))
|
|
35
|
+
|
|
36
|
+
if mycli.completion_refresher.is_refreshing():
|
|
37
|
+
result.append(("class:bottom-toolbar", " Refreshing completions..."))
|
|
38
|
+
|
|
39
|
+
return result
|
|
40
|
+
|
|
41
|
+
return get_toolbar_tokens
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _get_vi_mode():
|
|
45
|
+
"""Get the current vi mode for display."""
|
|
46
|
+
return {
|
|
47
|
+
InputMode.INSERT: "I",
|
|
48
|
+
InputMode.NAVIGATION: "N",
|
|
49
|
+
InputMode.REPLACE: "R",
|
|
50
|
+
InputMode.REPLACE_SINGLE: "R",
|
|
51
|
+
InputMode.INSERT_MULTIPLE: "M",
|
|
52
|
+
}[get_app().vi_state.input_mode]
|
mycli/compat.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
from .packages.special.main import COMMANDS
|
|
3
|
+
from collections import OrderedDict
|
|
4
|
+
|
|
5
|
+
from .sqlcompleter import SQLCompleter
|
|
6
|
+
from .sqlexecute import SQLExecute, ServerSpecies
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CompletionRefresher(object):
|
|
10
|
+
refreshers = OrderedDict()
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self._completer_thread = None
|
|
14
|
+
self._restart_refresh = threading.Event()
|
|
15
|
+
|
|
16
|
+
def refresh(self, executor, callbacks, completer_options=None):
|
|
17
|
+
"""Creates a SQLCompleter object and populates it with the relevant
|
|
18
|
+
completion suggestions in a background thread.
|
|
19
|
+
|
|
20
|
+
executor - SQLExecute object, used to extract the credentials to connect
|
|
21
|
+
to the database.
|
|
22
|
+
callbacks - A function or a list of functions to call after the thread
|
|
23
|
+
has completed the refresh. The newly created completion
|
|
24
|
+
object will be passed in as an argument to each callback.
|
|
25
|
+
completer_options - dict of options to pass to SQLCompleter.
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
if completer_options is None:
|
|
29
|
+
completer_options = {}
|
|
30
|
+
|
|
31
|
+
if self.is_refreshing():
|
|
32
|
+
self._restart_refresh.set()
|
|
33
|
+
return [(None, None, None, "Auto-completion refresh restarted.")]
|
|
34
|
+
else:
|
|
35
|
+
self._completer_thread = threading.Thread(
|
|
36
|
+
target=self._bg_refresh, args=(executor, callbacks, completer_options), name="completion_refresh"
|
|
37
|
+
)
|
|
38
|
+
self._completer_thread.daemon = True
|
|
39
|
+
self._completer_thread.start()
|
|
40
|
+
return [(None, None, None, "Auto-completion refresh started in the background.")]
|
|
41
|
+
|
|
42
|
+
def is_refreshing(self):
|
|
43
|
+
return self._completer_thread and self._completer_thread.is_alive()
|
|
44
|
+
|
|
45
|
+
def _bg_refresh(self, sqlexecute, callbacks, completer_options):
|
|
46
|
+
completer = SQLCompleter(**completer_options)
|
|
47
|
+
|
|
48
|
+
# Create a new pgexecute method to populate the completions.
|
|
49
|
+
e = sqlexecute
|
|
50
|
+
executor = SQLExecute(
|
|
51
|
+
e.dbname,
|
|
52
|
+
e.user,
|
|
53
|
+
e.password,
|
|
54
|
+
e.host,
|
|
55
|
+
e.port,
|
|
56
|
+
e.socket,
|
|
57
|
+
e.charset,
|
|
58
|
+
e.local_infile,
|
|
59
|
+
e.ssl,
|
|
60
|
+
e.ssh_user,
|
|
61
|
+
e.ssh_host,
|
|
62
|
+
e.ssh_port,
|
|
63
|
+
e.ssh_password,
|
|
64
|
+
e.ssh_key_filename,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# If callbacks is a single function then push it into a list.
|
|
68
|
+
if callable(callbacks):
|
|
69
|
+
callbacks = [callbacks]
|
|
70
|
+
|
|
71
|
+
while 1:
|
|
72
|
+
for refresher in self.refreshers.values():
|
|
73
|
+
refresher(completer, executor)
|
|
74
|
+
if self._restart_refresh.is_set():
|
|
75
|
+
self._restart_refresh.clear()
|
|
76
|
+
break
|
|
77
|
+
else:
|
|
78
|
+
# Break out of while loop if the for loop finishes natually
|
|
79
|
+
# without hitting the break statement.
|
|
80
|
+
break
|
|
81
|
+
|
|
82
|
+
# Start over the refresh from the beginning if the for loop hit the
|
|
83
|
+
# break statement.
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
for callback in callbacks:
|
|
87
|
+
callback(completer)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def refresher(name, refreshers=CompletionRefresher.refreshers):
|
|
91
|
+
"""Decorator to add the decorated function to the dictionary of
|
|
92
|
+
refreshers. Any function decorated with a @refresher will be executed as
|
|
93
|
+
part of the completion refresh routine."""
|
|
94
|
+
|
|
95
|
+
def wrapper(wrapped):
|
|
96
|
+
refreshers[name] = wrapped
|
|
97
|
+
return wrapped
|
|
98
|
+
|
|
99
|
+
return wrapper
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@refresher("databases")
|
|
103
|
+
def refresh_databases(completer, executor):
|
|
104
|
+
completer.extend_database_names(executor.databases())
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@refresher("schemata")
|
|
108
|
+
def refresh_schemata(completer, executor):
|
|
109
|
+
# schemata - In MySQL Schema is the same as database. But for mycli
|
|
110
|
+
# schemata will be the name of the current database.
|
|
111
|
+
completer.extend_schemata(executor.dbname)
|
|
112
|
+
completer.set_dbname(executor.dbname)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@refresher("tables")
|
|
116
|
+
def refresh_tables(completer, executor):
|
|
117
|
+
table_columns_dbresult = list(executor.table_columns())
|
|
118
|
+
completer.extend_relations(table_columns_dbresult, kind="tables")
|
|
119
|
+
completer.extend_columns(table_columns_dbresult, kind="tables")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@refresher("users")
|
|
123
|
+
def refresh_users(completer, executor):
|
|
124
|
+
completer.extend_users(executor.users())
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# @refresher('views')
|
|
128
|
+
# def refresh_views(completer, executor):
|
|
129
|
+
# completer.extend_relations(executor.views(), kind='views')
|
|
130
|
+
# completer.extend_columns(executor.view_columns(), kind='views')
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@refresher("functions")
|
|
134
|
+
def refresh_functions(completer, executor):
|
|
135
|
+
completer.extend_functions(executor.functions())
|
|
136
|
+
if executor.server_info.species == ServerSpecies.TiDB:
|
|
137
|
+
completer.extend_functions(completer.tidb_functions, builtin=True)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@refresher("special_commands")
|
|
141
|
+
def refresh_special(completer, executor):
|
|
142
|
+
completer.extend_special_commands(COMMANDS.keys())
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@refresher("show_commands")
|
|
146
|
+
def refresh_show_commands(completer, executor):
|
|
147
|
+
completer.extend_show_items(executor.show_candidates())
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@refresher("keywords")
|
|
151
|
+
def refresh_keywords(completer, executor):
|
|
152
|
+
if executor.server_info.species == ServerSpecies.TiDB:
|
|
153
|
+
completer.extend_keywords(completer.tidb_keywords, replace=True)
|