Skip to content

Commit

Permalink
update screenshots, add autocomplete, add blog post (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
tconbeer authored Dec 13, 2023
1 parent ae44c2f commit 839bcd5
Show file tree
Hide file tree
Showing 79 changed files with 2,098 additions and 2,109 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"cSpell.enabled": true
"cSpell.enabled": false
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .",
"format": "prettier --write --plugin prettier-plugin-svelte --plugin prettier-plugin-organize-imports --plugin prettier-plugin-tailwindcss ."
"format": "prettier --write --plugin prettier-plugin-svelte --plugin prettier-plugin-organize-imports --plugin prettier-plugin-tailwindcss .",
"themes": "svgo -f ./src/lib/assets/themes -o ./src/lib/assets/themes"
},
"pre-commit": [
"lint"
Expand All @@ -33,6 +34,7 @@
"rehype-slug": "^6.0.0",
"svelte": "^4.0.5",
"svelte-check": "^3.4.3",
"svgo": "^3.0.5",
"tailwindcss": "^3.3.3",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
Expand Down
103 changes: 103 additions & 0 deletions pnpm-lock.yaml

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

59 changes: 59 additions & 0 deletions src/blog/autocomplete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
title: "Autocomplete Comes to Harlequin"
publishedAt: 2023-12-14T12:00:00Z
lede: Less typing, more awesome.
---

<script>
import autocomplete from '$lib/assets/blog/autocomplete.mp4'
import arch from '$lib/assets/blog/autocomplete-arch.png'
</script>

You've been asking for it, and now you've got it: Harlequin v1.7.0 ships with a powerful autocomplete system for paths, words, and namespace members.

<video autoplay loop muted playsinline controls class="mt-6">
Your browser does not support .mp4
<source src={autocomplete} type="video/mp4" />
</video>

## Path Completion

After you type `/` or `\` (I see you, Windows users), Harlequin will suggest either relative or absolute paths to directories and files on your local filesystem. This also works inside quotes, so querying files with DuckDB just got a lot easier.

## Word Completion

If you type any word character, Harlequin will show completions that match. Harlequin has a (small) set of built-in completions for ANSI SQL keywords and functions (like `select` and `sum`), and adapters can provide additional completions for dialect-specific keywords, functions, and other relevant strings.

## Namespace Member Completion

After you type `.` or `:`, Harlequin will fetch completions whose "context" matches the string to the left of the separator. This means `schema.` will suggest any tables or views in that schema, `table.` will suggest columns, etc. It also works with quoted identifiers: `"my schema".` will do exactly what you want.

## How it works

### The UI

Harlequin is built using [Textual](https://textual.textualize.io/), a TUI framework for Python. The Query Editor uses a [supercharged fork](https://github.com/tconbeer/textual-textarea) of Textual's built-in [TextArea widget](https://textual.textualize.io/widget_gallery/#textarea), that wraps it in a container and provides a number of other features, including copy/paste, undo/redo and open/save.

The autocomplete list is built using Textual's [OptionList widget](https://textual.textualize.io/widget_gallery/#textarea), which gets mounted in the same container as the TextArea. The container defines two [layers](https://textual.textualize.io/guide/layout/#layers), which allows the OptionList to float above the TextArea. The OptionList in Harlequin cannot receive focus, so keys are always sent to the TextArea.

<img src={arch} alt="An architecture diagram showing the relationship of the TextArea and OptionList widgets and their parent container." class="my-6">

When you type in the TextArea, if the key is the right type to trigger completions, the TextArea updates its own state, which tracks which completer should be used, and then posts a [message](https://textual.textualize.io/guide/events/) that includes the preceding word (or path, etc.) to be matched. That message is handled by the parent container, which calls a method on the OptionList with the relevant completer function and its context; that method generates the completions in a separate thread. When completions are ready, the [thread worker](https://textual.textualize.io/guide/workers/#thread-workers) posts a message with the completions. On receiving that message, the OptionList updates its size and position, and sets a reactive variable that indicates that it should be visible.

Other keypresses work in a similar fashion, where messages to either hide the OptionList or insert the selection are posted by the TextArea and routed by the parent container. If you'd like to dive in farther, the [source for the OptionList is here](https://github.com/tconbeer/textual-textarea/blob/main/src/textual_textarea/autocomplete.py).

### Pluggable Completers

The architecture of the TextArea and OptionList allows any `Callable[str, tuple[str, str]]` to be used to generate completions. Harlequin does this with two callable classes, WordCompleter and MemberCompleter ([source](https://github.com/tconbeer/harlequin/blob/main/src/harlequin/autocomplete/completers.py)). Those classes store their relevant vocabularies and define their own matching logic. They are instantiated (in a separate thread) for the first time just after the Data Catalog is hydrated (since they rely on the same data for completions). The vocabularies are updated after any change to the Data Catalog.

Adapters can declare their own lists of additional completions, which are included in the vocabularies of the completers when they are first instantiated. The [DuckDB](https://github.com/tconbeer/harlequin/blob/main/src/harlequin_duckdb/completions.py) and [SQLite](https://github.com/tconbeer/harlequin/blob/main/src/harlequin_sqlite/completions.py) adapters do this by querying system tables for every keyword, function, pragma, etc.

Right now, the completers work using simple string operations on their vocabularies -- Tries are not necessary for now, even up to thousands of possible completions. But this may change in the future, as I look to support fuzzy matches, more contextual matches (using the Tree Sitter parse tree), and more advanced features .

## Try it out

If you haven't already tried Harlequin, there is no better time than now! Read [the docs](/docs/getting-started) or just shoot from the hip:

```bash
pipx install harlequin
```
86 changes: 47 additions & 39 deletions src/docs/bindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,43 +9,51 @@ menuOrder: 200

## General Bindings

- <Key>ctrl+q</Key>: Quit Harlequin
- <Key>F1</Key>: Show the help screen.
- <Key>F2</Key>: Focus on the Query Editor.
- <Key>F5</Key>: Focus on the Results Viewer.
- <Key>F6</Key>: Focus on the Data Catalog.
- <Key>F9</Key>, <Key>ctrl+b</Key>: Toggle the sidebar.
- <Key>F10</Key>: Toggle full screen mode for the current widget.
- <Key>ctrl+e</Key>: Write the returned data to a CSV, Parquet, or JSON file.
- <Key>ctrl+q</Key> Quit Harlequin
- <Key>F1</Key> Show the help screen.
- <Key>F2</Key> Focus on the Query Editor.
- <Key>F5</Key> Focus on the Results Viewer.
- <Key>F6</Key> Focus on the Data Catalog.
- <Key>F9</Key>, <Key>ctrl+b</Key> Toggle the sidebar.
- <Key>F10</Key> Toggle full screen mode for the current widget.
- <Key>ctrl+e</Key> Write the returned data to a CSV, Parquet, or JSON file.

## Query Editor Bindings

### Actions

- <Key>F4</Key>: Format the query.
- <Key>ctrl+enter</Key>, <Key>ctrl+j</Key>: Run the query.
- <Key>ctrl+o</Key>: Open a text file in the Query Editor.
- <Key>ctrl+s</Key>: Save the contents of the Query Editor to a file.
- <Key>ctrl+n</Key>: Create a new buffer (editor tab).
- <Key>ctrl+w</Key>: Close the current buffer (editor tab).
- <Key>ctrl+k</Key>: View the next buffer (editor tab).
- <Key>F4</Key> Format the query.
- <Key>ctrl+enter</Key>, <Key>ctrl+j</Key> Run the query.
- <Key>ctrl+o</Key> Open a text file in the Query Editor.
- <Key>ctrl+s</Key> Save the contents of the Query Editor to a file.
- <Key>ctrl+n</Key> Create a new buffer (editor tab).
- <Key>ctrl+w</Key> Close the current buffer (editor tab).
- <Key>ctrl+k</Key> View the next buffer (editor tab).

### Editing Text

- <Key>ctrl+a</Key>: Select all, move the cursor to the end of the query.
- <Key>ctrl+x</Key>: Cut selected text.
- <Key>ctrl+c</Key>: Copy selected text.
- <Key>ctrl+v</Key>, <Key>ctrl+u</Key>, <Key>shift+insert</Key>, <Key>Right Click</Key>: Paste selected text.
- <Key>ctrl+z</Key>: Undo.
- <Key>ctrl+y</Key>: Redo.
- <Key>ctrl+/</Key>, <Key>ctrl+\_</Key>: Toggle comments on selected line(s).
- <Key>tab</Key>: Insert spaces at cursor to move the cursor to the next tab stop, or indent the selected line(s) to the next tab stop.
- <Key>shift+tab</Key>: Dedent the selected line(s) to the next tab stop.
- <Key>shift+delete</Key>: Delete the current line.
- <Key>ctrl+a</Key> Select all, move the cursor to the end of the query.
- <Key>ctrl+x</Key> Cut selected text.
- <Key>ctrl+c</Key> Copy selected text.
- <Key>ctrl+v</Key>, <Key>ctrl+u</Key>, <Key>shift+insert</Key>, <Key>Right Click</Key> Paste selected text.
- <Key>ctrl+z</Key> Undo.
- <Key>ctrl+y</Key> Redo.
- <Key>ctrl+/</Key>, <Key>ctrl+\_</Key> Toggle comments on selected line(s).
- <Key>tab</Key> Insert spaces at cursor to move the cursor to the next tab stop, or indent the selected line(s) to the next tab stop.
- <Key>shift+tab</Key> Dedent the selected line(s) to the next tab stop.
- <Key>shift+delete</Key> Delete the current line.

### Using Autocomplete

_With the autocomplete list open:_

- <Key>up</Key>, <Key>down</Key>, <Key>PgUp</Key>, <Key>PgDn</Key> Select a different item in the list.
- <Key>tab</Key>, <Key>enter</Key> Place the current selection in the Query Editor.
- <Key>escape</Key> Dismiss the autocomplete list.

### Moving the Cursor

- <Key>up</Key>,<Key>down</Key>,<Key>left</Key>,<Key>right</Key>: Move the cursor one position.
- <Key>up</Key>,<Key>down</Key>,<Key>left</Key>,<Key>right</Key> Move the cursor one position.
- <Key>home</Key> Move the cursor to the start of the line.
- <Key>end</Key> Move the cursor to the end of the line.
- <Key>ctrl+home</Key> Move the cursor to the start of the query.
Expand All @@ -56,27 +64,27 @@ menuOrder: 200
- <Key>ctrl+down</Key> Scroll down one line.
- <Key>ctrl+left</Key> Move the cursor to the start of the current token.
- <Key>ctrl+right</Key> Move the cursor to the end of the current token.
- <Key>shift+[any]</Key>: Select text while moving the cursor.
- <Key>shift+[any]</Key> Select text while moving the cursor.

## Results Viewer Bindings

### Switching Tabs

- <Key>j</Key>: Switch to the previous tab.
- <Key>k</Key>: Switch to the next tab.
- <Key>j</Key> Switch to the previous tab.
- <Key>k</Key> Switch to the next tab.

### Moving the Cursor

- <Key>up</Key>,<Key>down</Key>,<Key>left</Key>,<Key>right</Key>: Move the cursor one cell.
- <Key>home</Key>: Move the cursor to the top of the current column.
- <Key>end</Key>: Move the cursor to the bottom of the current column.
- <Key>PgUp</Key>: Move the cursor up one screen.
- <Key>PgDn</Key>: Move the cursor down one screen.
- <Key>up</Key>,<Key>down</Key>,<Key>left</Key>,<Key>right</Key> Move the cursor one cell.
- <Key>home</Key> Move the cursor to the top of the current column.
- <Key>end</Key> Move the cursor to the bottom of the current column.
- <Key>PgUp</Key> Move the cursor up one screen.
- <Key>PgDn</Key> Move the cursor down one screen.

## Data Catalog Bindings

- <Key>enter</Key>: Select the current item.
- <Key>space</Key>: Toggle the expand/collapses state of the current item.
- <Key>up</Key>: Move the cursor up.
- <Key>down</Key>: Move the cursor down.
- <Key>ctrl+enter</Key>, <Key>ctrl+j</Key>: Insert the current name into the Query Editor.
- <Key>enter</Key> Select the current item.
- <Key>space</Key> Toggle the expand/collapses state of the current item.
- <Key>up</Key> Move the cursor up.
- <Key>down</Key> Move the cursor down.
- <Key>ctrl+enter</Key>, <Key>ctrl+j</Key> Insert the current name into the Query Editor.
Loading

0 comments on commit 839bcd5

Please sign in to comment.