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.
Files changed (71) hide show
  1. {harlequin-2.0.3 → harlequin-2.0.4}/PKG-INFO +1 -1
  2. {harlequin-2.0.3 → harlequin-2.0.4}/pyproject.toml +1 -1
  3. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/app.py +11 -14
  4. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/code_editor.py +55 -47
  5. {harlequin-2.0.3 → harlequin-2.0.4}/LICENSE +0 -0
  6. {harlequin-2.0.3 → harlequin-2.0.4}/README.md +0 -0
  7. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/__init__.py +0 -0
  8. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/__main__.py +0 -0
  9. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/actions.py +0 -0
  10. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/adapter.py +0 -0
  11. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/app.tcss +0 -0
  12. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/app_base.py +0 -0
  13. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/autocomplete/__init__.py +0 -0
  14. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/autocomplete/completers.py +0 -0
  15. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/autocomplete/completion.py +0 -0
  16. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/autocomplete/constants.py +0 -0
  17. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/bindings.py +0 -0
  18. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/cache.py +0 -0
  19. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/catalog.py +0 -0
  20. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/catalog_cache.py +0 -0
  21. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/cli.py +0 -0
  22. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/colors.py +0 -0
  23. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/__init__.py +0 -0
  24. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/confirm_modal.py +0 -0
  25. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/data_catalog/__init__.py +0 -0
  26. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/data_catalog/database_tree.py +0 -0
  27. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/data_catalog/file_tree.py +0 -0
  28. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/data_catalog/s3_tree.py +0 -0
  29. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/data_catalog/tree.py +0 -0
  30. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/error_modal.py +0 -0
  31. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/export_screen.py +0 -0
  32. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/help_screen.md +0 -0
  33. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/help_screen.py +0 -0
  34. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/history_screen.py +0 -0
  35. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/results_viewer.py +0 -0
  36. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/components/run_query_bar.py +0 -0
  37. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/config.py +0 -0
  38. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/config_wizard.py +0 -0
  39. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/copy_formats.py +0 -0
  40. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/copy_widgets.py +0 -0
  41. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/driver.py +0 -0
  42. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/editor_cache.py +0 -0
  43. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/exception.py +0 -0
  44. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/export.py +0 -0
  45. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/global.tcss +0 -0
  46. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/history.py +0 -0
  47. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/keymap.py +0 -0
  48. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/keys_app.py +0 -0
  49. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/keys_app.tcss +0 -0
  50. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/locale_manager.py +0 -0
  51. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/messages.py +0 -0
  52. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/options.py +0 -0
  53. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/plugins.py +0 -0
  54. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/py.typed +0 -0
  55. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/transaction_mode.py +0 -0
  56. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin/windows_timezone.py +0 -0
  57. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_duckdb/__init__.py +0 -0
  58. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_duckdb/adapter.py +0 -0
  59. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_duckdb/catalog.py +0 -0
  60. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_duckdb/cli_options.py +0 -0
  61. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_duckdb/completions.py +0 -0
  62. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_duckdb/interactions.py +0 -0
  63. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_duckdb/py.typed +0 -0
  64. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_sqlite/__init__.py +0 -0
  65. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_sqlite/adapter.py +0 -0
  66. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_sqlite/catalog.py +0 -0
  67. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_sqlite/cli_options.py +0 -0
  68. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_sqlite/completions.py +0 -0
  69. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_sqlite/interactions.py +0 -0
  70. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_sqlite/py.typed +0 -0
  71. {harlequin-2.0.3 → harlequin-2.0.4}/src/harlequin_vscode/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: harlequin
3
- Version: 2.0.3
3
+ Version: 2.0.4
4
4
  Summary: The SQL IDE for Your Terminal.
5
5
  Home-page: https://harlequin.sh
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "harlequin"
3
- version = "2.0.3"
3
+ version = "2.0.4"
4
4
  description = "The SQL IDE for Your Terminal."
5
5
  authors = ["Ted Conbeer <tconbeer@users.noreply.github.com>"]
6
6
  license = "MIT"
@@ -113,9 +113,9 @@ class QueryError(Message):
113
113
 
114
114
 
115
115
  class QuerySubmitted(Message):
116
- def __init__(self, query_text: str, limit: int | None) -> None:
116
+ def __init__(self, queries: list[str], limit: int | None) -> None:
117
117
  super().__init__()
118
- self.query_text = query_text.strip()
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
- query_text=self._get_query_text(),
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
- query_text=self._get_query_text(), limit=self.run_query_bar.limit_value
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
- query_text=self._get_query_text(),
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.query_text:
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 = self._split_query_text(message.query_text)
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 _get_query_text(self) -> str:
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
- @property
36
- def current_query(self) -> str:
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
- if not semicolons:
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
- first = (0, 0)
69
- second = (0, 0)
70
- for c in semicolons:
71
- if c <= self.selection.end:
72
- first = second
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 self.text_input.get_text_range(
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
- @property
132
- def _semicolons(self) -> list[tuple[int, int]]:
133
- semicolons: list[tuple[int, int]] = []
134
- for i, line in enumerate(self.text.splitlines()):
135
- for pos in [m.span()[1] for m in re.finditer(";", line)]:
136
- semicolons.append((i, pos))
137
- return semicolons
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