Skip to content

Commit

Permalink
feat: autocomplete (#351)
Browse files Browse the repository at this point in the history
* feat: autocomplete

* fix: lock deps

* fix: bump textarea, update readme
  • Loading branch information
tconbeer authored Dec 13, 2023
1 parent f741874 commit e905040
Show file tree
Hide file tree
Showing 23 changed files with 11,902 additions and 8,588 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.svg filter=lfs diff=lfs merge=lfs -text
*.mp4 filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
harlequin.svg !filter !diff !merge text
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ repos:
- duckdb>=0.8.0
- shandy-sqlfmt[jinjafmt]
- textual>=0.41.0
- textual-textarea>=0.8.0
- textual-textarea>=0.9.0
- textual-fastdatatable>=0.4.0
- pytest
- types-pygments
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@ All notable changes to this project will be documented in this file.

### Features

- AUTOCOMPLETE! Harlequin's query editor will now offer completions in a drop-down for SQL keywords, functions, and database objects (like tables, views, columns). With the autocomplete list open, use <kbd>up</kbd>, <kbd>down</kbd>, <kbd>PgUp</kbd>, <kbd>PgDn</kbd>, to select an option and <kbd>enter</kbd> or <kbd>Tab</kbd> to insert it into the editor.
- Harlequin now uses a new TextArea widget for its code editor. This improves performance for long queries, adds line numbers in a gutter, and changes the underlying engine for syntax highlighting from Pygments to Tree Sitter ([tconbeer/textual-textarea#123](https://github.com/tconbeer/textual-textarea/issues/123)).
- In the Query Editor: double-click to select a word, triple-click to select a line, and quadruple-click to select the entire query ([tconbeer/textual-textarea#111](https://github.com/tconbeer/textual-textarea/issues/111), [tconbeer/textual-textarea#112](https://github.com/tconbeer/textual-textarea/issues/112)).

### Changes

- Changes the default theme to `harlequin`.

### Adapter API Changes

- Many key types are now exported from the main `harlequin` package: `HarlequinAdapter`, `HarlequinConnection`, `HarlequinCursor`, `HarlequinAdapterOption`, `HarlequinCopyFormat`, `HarlequinCompletion`.
- `HarlequinConnection`s may now (optionally) define a `get_completions()` method, which should return a list of `HarlequinCompletion` instances; each returned completion will be available to users in the autocompletion list.

### Bug Fixes

- Fixes a bug that was causing an empty line to appear at the bottom of the Query Editor pane.
Expand Down
33 changes: 30 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@

The SQL IDE for Your Terminal.

![Harlequin](./static/themes/harlequin.svg)

![Harlequin](./harlequin.svg)

## Installing Harlequin

Expand All @@ -18,7 +17,7 @@ After installing Python 3.8 or above, install Harlequin using `pip` or `pipx` wi
pipx install harlequin
```

## Using Harlequin
## Using Harlequin with DuckDB

From any shell, to open one or more DuckDB database files:

Expand All @@ -32,6 +31,34 @@ To open an in-memory DuckDB session, run Harlequin with no arguments:
harlequin
```

If you want to control the version of DuckDB that Harlequin uses, see the [Troubleshooting](troubleshooting/duckdb-version-mismatch) page.

## Using Harlequin with SQLite and Other Adapters

Harlequin also ships with a SQLite3 adapter. You can open one or more SQLite database files with:

```bash
harlequin -a sqlite "path/to/sqlite.db" "another_sqlite.db"
```

Like DuckDB, you can also open an in-memory database by omitting the paths:

```bash
harlequin -a sqlite
```

Other adapters can be installed using `pip install <adapter package>` or `pipx inject harlequin <adapter package>`, depending on how you installed Harlequin. For a list of known adapters provided either by the Harlequin maintainers or the broader community, see the [adapters](https://harlequin.sh/docs/adapters) page in the docs.

## Getting Help

To view all command-line options for Harlequin and all installed adapters, after installation, simply type:

```bash
harlequin --help
```

To view a list of all key bindings (keyboard shortcuts) within the app, press <Key>F1</Key>. You can also view this list outside the app [in the docs](https://harlequin.sh/docs/bindings).

## More info at [harlequin.sh](https://harlequin.sh)

Visit [harlequin.sh](https://harlequin.sh) for an overview of features and full documentation.
Expand Down
212 changes: 212 additions & 0 deletions harlequin.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
78 changes: 41 additions & 37 deletions poetry.lock

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

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ python = ">=3.8.1,<4.0.0"
# textual and component libraries
textual = "==0.41.0"
textual-fastdatatable = "==0.4.0"
textual-textarea = "==0.8.0"
textual-textarea = "==0.9.2"

# click
click = "^8.1.3"
Expand All @@ -37,10 +37,10 @@ pyperclip = "^1.8.2"
importlib_metadata = { version = ">=4.6.0", python = "<3.10.0" }
tomli = { version = "^2.0.1", python = "<3.11.0" }
tomlkit = "^0.12.3"
questionary = "^2.0.1"

# database adapters (optional installs for extras)
harlequin-postgres = { version = "^0.1", optional = true }
questionary = "^2.0.1"

[tool.poetry.group.dev.dependencies]
pre-commit = "^3.3.1"
Expand Down
15 changes: 14 additions & 1 deletion src/harlequin/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
from harlequin.app import Harlequin as Harlequin
from harlequin.adapter import HarlequinAdapter, HarlequinConnection, HarlequinCursor
from harlequin.app import Harlequin
from harlequin.autocomplete import HarlequinCompletion
from harlequin.options import HarlequinAdapterOption, HarlequinCopyFormat

__all__ = [
"Harlequin",
"HarlequinAdapter",
"HarlequinConnection",
"HarlequinCursor",
"HarlequinCopyFormat",
"HarlequinAdapterOption",
"HarlequinCompletion",
]
14 changes: 14 additions & 0 deletions src/harlequin/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from textual_fastdatatable.backend import AutoBackendType

from harlequin.autocomplete.completion import HarlequinCompletion
from harlequin.catalog import Catalog
from harlequin.options import HarlequinAdapterOption, HarlequinCopyFormat

Expand Down Expand Up @@ -99,6 +100,19 @@ def get_catalog(self) -> Catalog:
"""
pass

def get_completions(self) -> list[HarlequinCompletion]:
"""
Returns a list of extra completions to make available to the Query Editor's
autocomplete. These could be dialect-specific functions, keywords, etc.
Harlequin ships with a basic list of common ANSI-SQL keywords and functions.
It is not necessary to provide completions for Catalog items, since Harlequin
will create those from the Catalog.
Returns: list[HarlequinCompletion]
"""
return []

def copy(
self, query: str, path: Path, format_name: str, options: dict[str, Any]
) -> None:
Expand Down
27 changes: 22 additions & 5 deletions src/harlequin/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
from textual.worker import Worker, WorkerState

from harlequin.adapter import HarlequinAdapter, HarlequinCursor
from harlequin.autocomplete import completer_factory
from harlequin.cache import BufferState, Cache, write_cache
from harlequin.catalog import CatalogItem, NewCatalog
from harlequin.catalog import Catalog, CatalogItem, NewCatalog
from harlequin.colors import HarlequinColors
from harlequin.components import (
CodeEditor,
Expand Down Expand Up @@ -254,7 +255,7 @@ def _on_worker_cancelled(self, message: Worker.StateChanged) -> None:

def _on_worker_error(self, message: Worker.StateChanged) -> None:
if (
message.worker.name == "_post_updated_catalog"
message.worker.name == "update_schema_data"
and message.worker.error is not None
):
self._push_error_modal(
Expand All @@ -277,6 +278,7 @@ def _on_worker_error(self, message: Worker.StateChanged) -> None:

def on_new_catalog(self, message: NewCatalog) -> None:
self.data_catalog.update_tree(message.catalog)
self.update_completers(message.catalog)

def on_queries_executed(self, message: QueriesExecuted) -> None:
self.results_viewer.clear_all_tables()
Expand Down Expand Up @@ -535,11 +537,26 @@ def _set_result_viewer_data(self, cursors: Dict[str, HarlequinCursor]) -> None:
ResultsFetched(cursors=cursors, errors=errors, elapsed=elapsed)
)

def update_schema_data(self) -> None:
self._post_updated_catalog()
@work(thread=True, exclusive=True, exit_on_error=True, group="completer_builders")
def update_completers(self, catalog: Catalog) -> None:
if (
self.editor_collection.word_completer is not None
and self.editor_collection.member_completer is not None
):
self.editor_collection.word_completer.update_catalog(catalog=catalog)
self.editor_collection.member_completer.update_catalog(catalog=catalog)
else:
extra_completions = self.connection.get_completions()
word_completer, member_completer = completer_factory(
catalog=catalog,
extra_completions=extra_completions,
type_color=self.app_colors.gray,
)
self.editor_collection.word_completer = word_completer
self.editor_collection.member_completer = member_completer

@work(thread=True, exclusive=True, exit_on_error=False, group="schema_updaters")
def _post_updated_catalog(self) -> None:
def update_schema_data(self) -> None:
catalog = self.connection.get_catalog()
self.post_message(NewCatalog(catalog=catalog))

Expand Down
13 changes: 13 additions & 0 deletions src/harlequin/autocomplete/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from harlequin.autocomplete.completers import (
MemberCompleter,
WordCompleter,
completer_factory,
)
from harlequin.autocomplete.completion import HarlequinCompletion

__all__ = [
"HarlequinCompletion",
"MemberCompleter",
"WordCompleter",
"completer_factory",
]
Loading

0 comments on commit e905040

Please sign in to comment.