Skip to content

Commit

Permalink
Bump textual, editor, table; add locale (#454)
Browse files Browse the repository at this point in the history
* feat: add locale; bump editor and table versions

* fix: update profile scripts

* fix: xfail word autocomplete due to scroll bar
  • Loading branch information
tconbeer authored Feb 7, 2024
1 parent ca5a3c8 commit caf2cdf
Show file tree
Hide file tree
Showing 34 changed files with 8,707 additions and 8,573 deletions.
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ repos:
- click
- duckdb>=0.8.0
- shandy-sqlfmt[jinjafmt]
- textual>=0.47.1
- textual-textarea>=0.10.0
- textual-fastdatatable>=0.6.0
- textual>=0.49.0
- textual-textarea>=0.11.0
- textual-fastdatatable>=0.7.0
- pytest
- types-pygments
- rich-click>=1.7.1
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ All notable changes to this project will be documented in this file.
### Features

- The Databricks adapter is now installable as an extra; use `pip install harlequin[databricks]`. Thank you [@alexmalins](https://github.com/alexmalins)!
- In the Results Viewer, values are now formatted based on their type. Numbers have separators based on the locale, and numbers, dates/times/etc., and bools are right-aligned. Null values are now shown as a dim `∅ null`, instead of a blank cell.
- Adds a `--locale` option to override the system locale for number formatting.

### Bug Fixes

- The result counts in the Query History view now contain thousands separators ([#437](https://github.com/tconbeer/harlequin/issues/437) - thank you, [@code-master-ajay](https://github.com/code-master-ajay)!).
- Harlequin no longer crashes when executing SQLite queries that return multiple types in a single column ([#453](https://github.com/tconbeer/harlequin/issues/453)).

### Performance

Expand Down
11 changes: 7 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ lint:

.PHONY: serve
serve:
textual run --dev -c harlequin -f .
textual run --dev -c harlequin -P None -f .

.PHONY: sqlite
sqlite:
textual run --dev -c harlequin -a sqlite
textual run --dev -c harlequin -P None -a sqlite

marketing: $(wildcard static/themes/*.svg) static/harlequin.gif

Expand All @@ -27,7 +27,10 @@ static/themes/%.svg: pyproject.toml src/scripts/export_screenshots.py
static/harlequin.gif: static/harlequin.mp4
ffmpeg -i static/harlequin.mp4 -vf "fps=24,scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 static/harlequin.gif

profiles: .profiles/buffers.html
profiles: .profiles/buffers.html .profiles/fast_query.html

.profiles/buffers.html: src/scripts/profile_buffers.py pyproject.toml $(shell find src/harlequin -type f)
pyinstrument -r html -o .profiles/buffers.html "src/scripts/profile_buffers.py"
pyinstrument -r html -o .profiles/buffers.html "src/scripts/profile_buffers.py"

.profiles/fast_query.html: src/scripts/profile_fast_query.py pyproject.toml $(shell find src/harlequin -type f)
pyinstrument -r html -o .profiles/fast_query.html "src/scripts/profile_fast_query.py"
22 changes: 11 additions & 11 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ build-backend = "poetry.core.masonry.api"
python = ">=3.8.1,<4.0.0"

# textual and component libraries
textual = "==0.47.1"
textual-fastdatatable = "==0.6.3"
textual-textarea = "==0.10.0"
textual = "==0.49.0"
textual-fastdatatable = "==0.7.0"
textual-textarea = "==0.11.0"

# click
click = "^8.1.3"
Expand Down
13 changes: 10 additions & 3 deletions src/harlequin/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,15 @@ def handle_query_error(self, message: QueryError) -> None:
error=message.error,
)

@on(DataTable.DataLoadError)
def handle_data_load_error(self, message: DataTable.DataLoadError) -> None:
header = getattr(message.error, "title", message.error.__class__.__name__)
self._push_error_modal(
title="Query Error",
header=header,
error=message.error,
)

@on(NewCatalog)
def update_tree_and_completers(self, message: NewCatalog) -> None:
self.catalog = message.catalog
Expand Down Expand Up @@ -637,9 +646,7 @@ async def action_quit(self) -> None:
for i, editor in enumerate(self.editor_collection.all_editors):
if editor == self.editor_collection.current_editor:
focus_index = i
buffers.append(
BufferState(editor.cursor, editor.selection_anchor, editor.text)
)
buffers.append(BufferState(editor.selection, editor.text))
write_editor_cache(Cache(focus_index=focus_index, buffers=buffers))
update_catalog_cache(
connection_hash=self.connection_hash,
Expand Down
1 change: 0 additions & 1 deletion src/harlequin/catalog_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from typing import TYPE_CHECKING, Any, Sequence

from platformdirs import user_cache_dir
from textual_textarea.key_handlers import Cursor as Cursor

from harlequin.catalog import Catalog
from harlequin.history import History
Expand Down
23 changes: 21 additions & 2 deletions src/harlequin/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
from harlequin.config_wizard import wizard
from harlequin.exception import (
HarlequinConfigError,
HarlequinLocaleError,
HarlequinTzDataError,
pretty_print_error,
)
from harlequin.locale_manager import set_locale
from harlequin.plugins import load_plugins
from harlequin.windows_timezone import check_and_install_tzdata

Expand Down Expand Up @@ -75,6 +77,7 @@
"--limit",
"--config",
"--config-path",
"--locale",
"--no-download-tzdata",
"--version",
"--help",
Expand Down Expand Up @@ -216,6 +219,13 @@ def build_cli() -> click.Command:
expose_value=True,
is_eager=True,
)
@click.option(
"--locale",
help=(
"Provide a locale string (e.g., `en_US.UTF-8`) to override "
"the system locale for number formatting."
),
)
@click.option(
"--no-download-tzdata",
help=(
Expand Down Expand Up @@ -268,6 +278,15 @@ def inner_cli(
pretty_print_error(e)
ctx.exit(2)

# set the locale so we display numbers properly. Empty string uses system
# default
locale_config: str = config.pop("locale", "") # type: ignore
try:
set_locale(locale_config)
except HarlequinLocaleError as e:
pretty_print_error(e)
ctx.exit(2)

# remove the harlequin config from the options passed to the adapter
conn_str: Sequence[str] = config.pop("conn_str", tuple()) # type: ignore
if isinstance(conn_str, str):
Expand All @@ -286,8 +305,8 @@ def inner_cli(
show_s3: str | None = config.pop("show_s3", None) # type: ignore

# load and instantiate the adapter
adapter = config.pop("adapter", DEFAULT_ADAPTER)
adapter_cls: type[HarlequinAdapter] = adapters[adapter] # type: ignore
adapter: str = config.pop("adapter", DEFAULT_ADAPTER) # type: ignore
adapter_cls: type[HarlequinAdapter] = adapters[adapter]
try:
adapter_instance = adapter_cls(conn_str=conn_str, **config)
except HarlequinConfigError as e:
Expand Down
37 changes: 15 additions & 22 deletions src/harlequin/components/code_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@
from textual.css.query import NoMatches
from textual.message import Message
from textual.widgets import ContentSwitcher, TabbedContent, TabPane, Tabs
from textual_textarea import TextArea, TextAreaSaved
from textual_textarea.key_handlers import Cursor
from textual_textarea import TextAreaSaved, TextEditor

from harlequin.autocomplete import MemberCompleter, WordCompleter
from harlequin.components.error_modal import ErrorModal
from harlequin.editor_cache import BufferState, load_cache


class CodeEditor(TextArea):
class CodeEditor(TextEditor):
BINDINGS = [
Binding(
"ctrl+enter",
Expand Down Expand Up @@ -50,17 +49,17 @@ def current_query(self) -> str:
if not semicolons:
return self.text

before = Cursor(0, 0)
after: Union[None, Cursor] = None
before = (0, 0)
after: Union[None, tuple[int, int]] = None
for c in semicolons:
if c <= self.cursor:
if c <= self.selection.end:
before = c
elif after is None and c > self.cursor:
elif after is None and c > self.selection.end:
after = c
break
else:
lno = self.text_input.document.line_count - 1
after = Cursor(lno, len(self.text_input.document.get_line(lno)))
after = (lno, len(self.text_input.document.get_line(lno)))
return self.text_input.get_text_range(
start=(before[0], before[1]), end=(after[0], after[1])
)
Expand All @@ -72,13 +71,13 @@ def previous_query(self) -> str:
if not semicolons:
return self.text

first = Cursor(0, 0)
second = Cursor(0, 0)
first = (0, 0)
second = (0, 0)
for c in semicolons:
if c <= self.cursor:
if c <= self.selection.end:
first = second
second = c
elif c > self.cursor:
elif c > self.selection.end:
break

return self.text_input.get_text_range(
Expand Down Expand Up @@ -124,17 +123,12 @@ def action_format(self) -> None:
else:
self.text_input.selection = old_selection

def _get_text_between_cursors(self, before: Cursor, after: Cursor) -> str:
return self.text_input.get_text_range(
start=(before[0], before[1]), end=(after[0], after[1])
)

@property
def _semicolons(self) -> List[Cursor]:
semicolons: List[Cursor] = []
def _semicolons(self) -> list[tuple[int, int]]:
semicolons: list[tuple[int, int]] = []
for i, line in enumerate(self.text.splitlines()):
for pos in [m.span()[1] for m in re.finditer(";", line)]:
semicolons.append(Cursor(i, pos))
semicolons.append((i, pos))
return semicolons


Expand Down Expand Up @@ -271,8 +265,7 @@ async def action_new_buffer(
)
await self.add_pane(pane) # type: ignore
if state is not None:
editor.cursor = state.cursor
editor.selection_anchor = state.selection_anchor
editor.selection = state.selection
else:
self.active = new_tab_id
try:
Expand Down
3 changes: 1 addition & 2 deletions src/harlequin/components/results_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,11 @@ def clear_all_tables(self) -> None:
def get_visible_table(self) -> Union[ResultsTable, None]:
content = self.query_one(ContentSwitcher)
active_tab_id = self.active
self.log("ACTIVE TAB ID:", active_tab_id)
if active_tab_id:
try:
tab_pane = content.query_one(f"#{active_tab_id}", TabPane)
return tab_pane.query_one(ResultsTable)
except NoMatches:
self.log("NO MATCHES FOR ACTIVE TAB ID:", active_tab_id)
return None
else:
tables = content.query(ResultsTable)
Expand All @@ -84,6 +82,7 @@ async def push_table(
max_rows=self.max_results,
cursor_type="range",
max_column_content_width=self.max_col_width,
null_rep="[dim]∅ null[/]",
)
n = self.tab_count + 1
if n > 1:
Expand Down
10 changes: 10 additions & 0 deletions src/harlequin/config_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ def _wizard() -> None:
style=HARLEQUIN_QUESTIONARY_STYLE,
).unsafe_ask()

locale = questionary.text(
message="What locale should Harlequin use for formatting numbers?",
instruction="Leave blank to use the system locale.",
default=selected_profile.get("locale", ""),
style=HARLEQUIN_QUESTIONARY_STYLE,
).unsafe_ask()

adapter_cls = adapters[adapter]
adapter_option_choices = (
[
Expand Down Expand Up @@ -140,6 +147,9 @@ def _wizard() -> None:
if show_s3:
new_profile["show_s3"] = show_s3

if locale:
new_profile["locale"] = locale

new_profile.update(adapter_options)

_confirm_profile_generation(default_profile, profile_name, new_profile)
Expand Down
7 changes: 3 additions & 4 deletions src/harlequin/editor_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
from typing import List, Union

from platformdirs import user_cache_dir
from textual_textarea.key_handlers import Cursor as Cursor
from textual.widgets.text_area import Selection

CACHE_VERSION = 0
CACHE_VERSION = 1


@dataclass
class BufferState:
cursor: Cursor
selection_anchor: Union[Cursor, None]
selection: Selection
text: str


Expand Down
Loading

0 comments on commit caf2cdf

Please sign in to comment.