harlequin 2.0.3__tar.gz → 2.0.4__tar.gz
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.
- {harlequin-2.0.3 → harlequin-2.0.4}/PKG-INFO +1 -1
- {harlequin-2.0.3 → harlequin-2.0.4}/pyproject.toml +1 -1
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/app.py +11 -14
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/code_editor.py +55 -47
- {harlequin-2.0.3 → harlequin-2.0.4}/LICENSE +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/README.md +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/__init__.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/__main__.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/actions.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/adapter.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/app.tcss +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/app_base.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/autocomplete/__init__.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/autocomplete/completers.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/autocomplete/completion.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/autocomplete/constants.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/bindings.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/cache.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/catalog.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/catalog_cache.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/cli.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/colors.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/__init__.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/confirm_modal.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/data_catalog/__init__.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/data_catalog/database_tree.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/data_catalog/file_tree.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/data_catalog/s3_tree.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/data_catalog/tree.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/error_modal.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/export_screen.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/help_screen.md +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/help_screen.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/history_screen.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/results_viewer.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/run_query_bar.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/config.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/config_wizard.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/copy_formats.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/copy_widgets.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/driver.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/editor_cache.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/exception.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/export.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/global.tcss +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/history.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/keymap.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/keys_app.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/keys_app.tcss +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/locale_manager.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/messages.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/options.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/plugins.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/py.typed +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/transaction_mode.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/windows_timezone.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_duckdb/__init__.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_duckdb/adapter.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_duckdb/catalog.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_duckdb/cli_options.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_duckdb/completions.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_duckdb/interactions.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_duckdb/py.typed +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_sqlite/__init__.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_sqlite/adapter.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_sqlite/catalog.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_sqlite/cli_options.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_sqlite/completions.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_sqlite/interactions.py +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_sqlite/py.typed +0 -0
- {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_vscode/__init__.py +0 -0
|
@@ -113,9 +113,9 @@ class QueryError(Message):
|
|
|
113
113
|
|
|
114
114
|
|
|
115
115
|
class QuerySubmitted(Message):
|
|
116
|
-
def __init__(self,
|
|
116
|
+
def __init__(self, queries: list[str], limit: int | None) -> None:
|
|
117
117
|
super().__init__()
|
|
118
|
-
self.
|
|
118
|
+
self.queries = queries
|
|
119
119
|
self.limit = limit
|
|
120
120
|
self.submitted_at = time.monotonic()
|
|
121
121
|
|
|
@@ -317,7 +317,7 @@ class Harlequin(AppBase):
|
|
|
317
317
|
message.stop()
|
|
318
318
|
self.post_message(
|
|
319
319
|
QuerySubmitted(
|
|
320
|
-
|
|
320
|
+
queries=self._get_selected_queries(),
|
|
321
321
|
limit=self.run_query_bar.limit_value,
|
|
322
322
|
)
|
|
323
323
|
)
|
|
@@ -365,7 +365,8 @@ class Harlequin(AppBase):
|
|
|
365
365
|
message.stop()
|
|
366
366
|
self.post_message(
|
|
367
367
|
QuerySubmitted(
|
|
368
|
-
|
|
368
|
+
queries=self._get_selected_queries(),
|
|
369
|
+
limit=self.run_query_bar.limit_value,
|
|
369
370
|
)
|
|
370
371
|
)
|
|
371
372
|
|
|
@@ -512,7 +513,7 @@ class Harlequin(AppBase):
|
|
|
512
513
|
):
|
|
513
514
|
self.post_message(
|
|
514
515
|
QuerySubmitted(
|
|
515
|
-
|
|
516
|
+
queries=self._get_selected_queries(),
|
|
516
517
|
limit=self.run_query_bar.limit_value,
|
|
517
518
|
)
|
|
518
519
|
)
|
|
@@ -755,7 +756,7 @@ class Harlequin(AppBase):
|
|
|
755
756
|
def execute_query(self, message: QuerySubmitted) -> None:
|
|
756
757
|
if self.connection is None:
|
|
757
758
|
return
|
|
758
|
-
if message.
|
|
759
|
+
if message.queries:
|
|
759
760
|
self.full_screen = False
|
|
760
761
|
self.run_query_bar.set_not_responsive()
|
|
761
762
|
self.results_viewer.show_loading()
|
|
@@ -1005,7 +1006,7 @@ class Harlequin(AppBase):
|
|
|
1005
1006
|
if self.connection is None:
|
|
1006
1007
|
return
|
|
1007
1008
|
cursors: Dict[str, tuple[HarlequinCursor, str]] = {}
|
|
1008
|
-
queries =
|
|
1009
|
+
queries = message.queries
|
|
1009
1010
|
ddl_queries: list[str] = []
|
|
1010
1011
|
for q in queries:
|
|
1011
1012
|
try:
|
|
@@ -1043,14 +1044,10 @@ class Harlequin(AppBase):
|
|
|
1043
1044
|
self.connection.cancel()
|
|
1044
1045
|
self.post_message(QueriesCanceled())
|
|
1045
1046
|
|
|
1046
|
-
def
|
|
1047
|
+
def _get_selected_queries(self) -> list[str]:
|
|
1047
1048
|
if self.editor is None:
|
|
1048
|
-
return
|
|
1049
|
-
return (
|
|
1050
|
-
self._validate_selection()
|
|
1051
|
-
or self.editor.current_query
|
|
1052
|
-
or self.editor.previous_query
|
|
1053
|
-
)
|
|
1049
|
+
return []
|
|
1050
|
+
return self.editor.selected_queries()
|
|
1054
1051
|
|
|
1055
1052
|
@staticmethod
|
|
1056
1053
|
def _split_query_text(query_text: str) -> List[str]:
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import re
|
|
4
3
|
from typing import List, Union
|
|
5
4
|
|
|
6
5
|
from rich.text import TextType
|
|
@@ -20,6 +19,8 @@ from harlequin.messages import WidgetMounted
|
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
class CodeEditor(TextEditor, inherit_bindings=False):
|
|
22
|
+
SEMICOLON_QUERY = '(";" @semicolon)'
|
|
23
|
+
|
|
23
24
|
class Submitted(Message, bubble=True):
|
|
24
25
|
"""Posted when user runs the query.
|
|
25
26
|
|
|
@@ -32,56 +33,43 @@ class CodeEditor(TextEditor, inherit_bindings=False):
|
|
|
32
33
|
super().__init__()
|
|
33
34
|
self.text = text
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
def selected_queries(self) -> list[str]:
|
|
37
|
+
"""
|
|
38
|
+
Returns the list of queries that intersect
|
|
39
|
+
with the current selection.
|
|
40
|
+
"""
|
|
37
41
|
if self.text_input is None:
|
|
38
|
-
return
|
|
39
|
-
semicolons = self._semicolons
|
|
42
|
+
return []
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
return self.text
|
|
43
|
-
|
|
44
|
-
before = (0, 0)
|
|
45
|
-
after: Union[None, tuple[int, int]] = None
|
|
46
|
-
for c in semicolons:
|
|
47
|
-
if c <= self.selection.end:
|
|
48
|
-
before = c
|
|
49
|
-
elif after is None and c > self.selection.end:
|
|
50
|
-
after = c
|
|
51
|
-
break
|
|
52
|
-
else:
|
|
53
|
-
lno = self.text_input.document.line_count - 1
|
|
54
|
-
after = (lno, len(self.text_input.document.get_line(lno)))
|
|
55
|
-
return self.text_input.get_text_range(
|
|
56
|
-
start=(before[0], before[1]), end=(after[0], after[1])
|
|
57
|
-
).strip()
|
|
58
|
-
|
|
59
|
-
@property
|
|
60
|
-
def previous_query(self) -> str:
|
|
61
|
-
if self.text_input is None:
|
|
62
|
-
return ""
|
|
63
|
-
semicolons = self._semicolons
|
|
44
|
+
semicolons = self._query_semicolons()
|
|
64
45
|
|
|
65
46
|
if not semicolons:
|
|
66
|
-
return self.text
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
second = c
|
|
74
|
-
elif c > self.selection.end:
|
|
47
|
+
return [self.text]
|
|
48
|
+
|
|
49
|
+
queries: list[str] = []
|
|
50
|
+
prev_query: str | None = None
|
|
51
|
+
query_start = (0, 0)
|
|
52
|
+
for query_end in [*semicolons, self.text_input.document.end]:
|
|
53
|
+
if query_start > self.selection.end:
|
|
75
54
|
break
|
|
55
|
+
q = self.text_input.get_text_range(start=query_start, end=query_end).strip()
|
|
56
|
+
if q and query_end >= self.selection.start:
|
|
57
|
+
queries.append(q)
|
|
58
|
+
elif q:
|
|
59
|
+
prev_query = q
|
|
60
|
+
query_start = query_end
|
|
61
|
+
|
|
62
|
+
if not queries and prev_query:
|
|
63
|
+
return [prev_query]
|
|
76
64
|
|
|
77
|
-
return
|
|
78
|
-
start=(first[0], first[1]), end=(second[0], second[1])
|
|
79
|
-
).strip()
|
|
65
|
+
return queries
|
|
80
66
|
|
|
81
67
|
def on_mount(self) -> None:
|
|
82
68
|
self.post_message(EditorCollection.EditorSwitched(active_editor=self))
|
|
83
69
|
self.post_message(WidgetMounted(widget=self))
|
|
84
70
|
self.has_shown_clipboard_error = False
|
|
71
|
+
self.has_shown_tree_sitter_error = False
|
|
72
|
+
self._semicolon_query = self.prepare_query(self.SEMICOLON_QUERY)
|
|
85
73
|
|
|
86
74
|
def on_unmount(self) -> None:
|
|
87
75
|
self.post_message(EditorCollection.EditorSwitched(active_editor=None))
|
|
@@ -128,13 +116,33 @@ class CodeEditor(TextEditor, inherit_bindings=False):
|
|
|
128
116
|
if hasattr(self.app, "action_focus_data_catalog"):
|
|
129
117
|
self.app.action_focus_data_catalog()
|
|
130
118
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
119
|
+
def _query_semicolons(self) -> list[tuple[int, int]]:
|
|
120
|
+
if self.text_input is None:
|
|
121
|
+
return []
|
|
122
|
+
|
|
123
|
+
if self.text_input.is_syntax_aware:
|
|
124
|
+
assert self._semicolon_query is not None
|
|
125
|
+
query_result = self.query_syntax_tree(query=self._semicolon_query)
|
|
126
|
+
return [n.end_point for n in query_result.get("semicolon", [])]
|
|
127
|
+
else:
|
|
128
|
+
# tree-sitter is not installed. naively split on semicolons and
|
|
129
|
+
# show a warning.
|
|
130
|
+
import re
|
|
131
|
+
|
|
132
|
+
if not self.has_shown_tree_sitter_error:
|
|
133
|
+
self.app.notify(
|
|
134
|
+
"Tree-sitter is not installed. Syntax highlighting and query "
|
|
135
|
+
"splitting may not work as expected.\n"
|
|
136
|
+
"See https://harlequin.sh/docs/troubleshooting/tree-sitter",
|
|
137
|
+
severity="warning",
|
|
138
|
+
timeout=10,
|
|
139
|
+
)
|
|
140
|
+
self.has_shown_tree_sitter_error = True
|
|
141
|
+
semicolons: list[tuple[int, int]] = []
|
|
142
|
+
for i, line in enumerate(self.text.splitlines()):
|
|
143
|
+
for pos in [m.span()[1] for m in re.finditer(";", line)]:
|
|
144
|
+
semicolons.append((i, pos))
|
|
145
|
+
return semicolons
|
|
138
146
|
|
|
139
147
|
|
|
140
148
|
class EditorCollection(TabbedContent):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|