Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ For more detail, see:
install
supported-files
navigation
vim-mode
contig-actions
theme-editor
comparison-view
Expand Down
219 changes: 219 additions & 0 deletions docs/vim-mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# Vim Mode

Vim mode adds keyboard-driven navigation and commands. Enable it from the
settings panel with the `Vim Mode` toggle. When Vim mode is off, these Vim keys
are ignored and the normal app shortcuts keep their usual behavior.

When Vim mode is on, a single-line command bar is shown at the bottom of the
window. It is used for commands, searches, counts, and short status messages.

## Normal-mode keys

These keys work when Vim mode is enabled and the command bar is not actively
editing a `:` command or `/` search.

| Key | Action |
| --- | --- |
| `h` | Pan left by one pan step. |
| `l` | Pan right by one pan step. |
| `j` | Zoom out one step. |
| `k` | Zoom in one step. |
| `gg` | Jump to the start of the current sequence. |
| `<number>gg` | Jump to that 1-based position in the current sequence. For example, `500gg` jumps to position 500. |
| `G` | Jump to the end of the current sequence. |
| `gc`, `]c` | Jump to the start of the next contig. |
| `gC`, `[c` | Jump to the start of the previous contig. |
| `n` | Move to the next search hit. |
| `N` | Move to the previous search hit. |
| `w` | Move to the next annotation feature when a feature is selected. |
| `b` | Move to the previous annotation feature when a feature is selected. |
| `m<letter>` | Save the current view as a mark. For example, `ma` sets mark `a`. |
| `'<letter>` | Load a mark. For example, `'a` jumps to mark `a`. |
| `gv` | Toggle between single genome view and comparison view. |
| `ZZ` | Quit the app. |
| `:` | Open the Vim command prompt. |
| `/` | Open the Vim search prompt. |

The pan and zoom keys apply to the active view. Browser-only navigation, such as
`gg`, `G`, contig jumps, feature jumps, `go`, and Vim `/` search, requires the
single genome browser view.

## Counts

Counts can be typed before several normal-mode commands. The count is shown in
the command bar while it is being entered.

Supported counted commands:

| Command | Meaning |
| --- | --- |
| `<number>h` | Pan left by that many pan steps. |
| `<number>l` | Pan right by that many pan steps. |
| `<number>n` | Move forward by that many search hits. |
| `<number>N` | Move backward by that many search hits. |
| `<number>gg` | Jump to that position in the current sequence. |
| `<number>gc`, `<number>]c` | Jump forward by that many contigs. |
| `<number>gC`, `<number>[c` | Jump backward by that many contigs. |
| `<number>w` | Move forward by that many annotation features. |
| `<number>b` | Move backward by that many annotation features. |

Contig navigation clamps at the first or last contig. Feature navigation clamps
at the first or last feature. In concatenate view, feature navigation continues
across contig boundaries and skips contigs that have no annotation features.

## Marks

Marks save and restore view positions.

- `m<letter>` saves the current view to a mark.
- `'<letter>` loads that mark.

For example:

- `ma` saves mark `a`
- `'a` jumps to mark `a`

When a mark is saved, the command bar shows `mark <letter> set`. When a mark is
loaded, it shows `jumped to mark <letter>`.

Marks use the same view-state saving code as the numbered temporary view slots.
Browser view and comparison view keep separate saved-view banks.

## Annotation feature navigation

Feature navigation works after selecting an annotation feature.

- `w` selects the next annotation feature.
- `b` selects the previous annotation feature.
- Counts work, such as `5w` or `3b`.

In concatenate view, moving past the final feature of one contig continues to the
first feature of the next contig with annotations. Moving backward from the first
feature of a contig continues to the previous contig with annotations.

The command bar shows the selected feature label and its position within the
current contig's feature list, such as `geneA 12/80`. At the ends, it shows
`first feature` or `last feature`.

## Search result navigation

After using the search panel or Vim `/` search, use:

- `n` for the next hit
- `N` for the previous hit

Counts are supported, such as `5n` or `2N`.

The command bar shows the selected search result as `search hit x/y`. Clicking a
search result in the search panel also updates this Vim message.

## Command prompt

Press `:` to type a Vim command. Press `Enter` to run it. Press `Escape` to exit
the prompt without running it.

If a `:` command is active, `Escape` only exits the command prompt. It does not
also close panels. If the command prompt is not active, `Escape` keeps the app's
normal behavior.

Implemented commands:

| Command | Action |
| --- | --- |
| `:go <position>` | Jump to a 1-based position in the current sequence. |
| `:go <start>-<end>` | Jump to a range in the current sequence. |
| `:go <sequence>:<position>` | Jump to a position in a named sequence. |
| `:go <sequence>:<start>-<end>` | Jump to a range in a named sequence. |
| `:go <sequence> <position>` | Space-separated form for a named sequence. |
| `:go <sequence> <start>-<end>` | Space-separated range form for a named sequence. |
| `:open` | Open the native file picker, like pressing the open file button. |
| `:open <path>` | Open a file from a filesystem path. |
| `:download <accession>` | Open the download panel, fill the accession box, and start download-and-load. |
| `:colorscheme <theme>` | Switch to a theme. This uses standard Vim-style `colorscheme` syntax. |
| `:view single` | Switch to single genome view. |
| `:view comparison` | Switch to comparison view. |
| `:q` | Quit the app. |
| `:quit` | Quit the app. |

Position and range examples:

```text
:go 123
:go 100-250
:go contig1:123
:go contig1:100-250
:go sample:contig1:500
:go sample:contig1 500
```

The colon form is the preferred sequence syntax. The space-separated sequence
form is also available when it is clearer for sequence names that contain
colons.

Open examples:

```text
:open
:open /path/to/foo.fasta
:open "/path/to/foo bar.fasta"
```

Download example:

```text
:download GCF_000005845.2
```

## Search prompt

Press `/` to type a search. Press `Enter` to run it. Press `Escape` to exit the
prompt without running it.

Searches open the search panel with the results.

The first word chooses the search type:

- `annotation`, or any unambiguous prefix such as `a`, `an`, or `annot`
- `dna`, or any unambiguous prefix such as `d` or `dn`

Annotation search examples:

```text
/a gene1
/an contig1 gene1
/annot contig2 foo bar
```

DNA search examples:

```text
/d ACGT
/dna contig24 gtcgtacgtcag
```

If the search prompt contains a single word made only of `A`, `C`, `G`, and `T`
characters, it is treated as a whole-genome DNA search:

```text
/ACGT
```

For annotation and DNA searches, if the first word after the search type matches
a sequence name, the search is restricted to that sequence. Otherwise the search
runs across all sequences and the rest of the text is treated as the query.

## Completion and history

Press `Tab` in the command bar to cycle through available completions.

Completion is implemented for:

- `:` command names, such as `go`, `open`, `download`, `colorscheme`, `view`, `q`, and `quit`
- sequence names after `:go`
- theme names after `:colorscheme`
- `single` and `comparison` after `:view`
- sequence names after the search target in `/annotation` or `/dna` searches

Press `Up` and `Down` in the command bar to move through prompt history.
History is session-only, keeps the last 100 commands, and is stored separately
for `:` commands and `/` searches.
96 changes: 96 additions & 0 deletions project.godot
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,97 @@ seqhiker_close_right_panel={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194305,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
seqhiker_vim_scroll_left={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":72,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
seqhiker_vim_scroll_right={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":76,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
seqhiker_vim_zoom_out={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":74,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
seqhiker_vim_zoom_in={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":75,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
seqhiker_vim_command={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":58,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":59,"physical_keycode":0,"key_label":0,"unicode":58,"location":0,"echo":false,"script":null)
]
}
seqhiker_vim_search={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":47,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
seqhiker_vim_search_next={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":78,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
seqhiker_vim_search_previous={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":78,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
seqhiker_vim_feature_next={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":87,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
seqhiker_vim_feature_previous={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":66,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
seqhiker_vim_go_prefix={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":71,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
seqhiker_vim_go_end={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":71,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
seqhiker_vim_contig_next={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":67,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
seqhiker_vim_contig_previous={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":67,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
seqhiker_vim_contig_next_prefix={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":93,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
seqhiker_vim_contig_previous_prefix={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":91,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
seqhiker_vim_mark_save={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":77,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
seqhiker_vim_mark_load={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":39,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
seqhiker_toggle_search={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":70,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
Expand All @@ -85,6 +176,11 @@ seqhiker_go={
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":true,"pressed":false,"keycode":71,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
seqhiker_toggle_view_mode={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":4194306,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
seqhiker_view_slot_load_1={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":49,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
Expand Down
33 changes: 33 additions & 0 deletions scenes/Main.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,11 @@ max_value = 3.0
step = 0.05
value = 1.5

[node name="VimModeToggle" type="CheckButton" parent="Root/ContentMargin/ViewportLayer/SettingsPanel/SettingsMargin/SettingsLayout/SettingsScroll/SettingsPadding/SettingsContent"]
custom_minimum_size = Vector2(0, 24)
layout_mode = 2
text = "Vim Mode"

[node name="SoundsToggle" type="CheckButton" parent="Root/ContentMargin/ViewportLayer/SettingsPanel/SettingsMargin/SettingsLayout/SettingsScroll/SettingsPadding/SettingsContent"]
layout_mode = 2
tooltip_text = "Do your genoming like in the movies!
Expand Down Expand Up @@ -668,3 +673,31 @@ autowrap_mode = 3

[node name="FeaturePanel" parent="Root/ContentMargin/ViewportLayer" instance=ExtResource("3_30wbj")]
layout_mode = 1

[node name="VimCommandBar" type="PanelContainer" parent="Root"]
visible = false
custom_minimum_size = Vector2(0, 34)
layout_mode = 2
size_flags_horizontal = 3

[node name="VimCommandMargin" type="MarginContainer" parent="Root/VimCommandBar"]
layout_mode = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 4
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 4

[node name="VimCommandRow" type="HBoxContainer" parent="Root/VimCommandBar/VimCommandMargin"]
layout_mode = 2
theme_override_constants/separation = 4

[node name="VimCommandPrefix" type="Label" parent="Root/VimCommandBar/VimCommandMargin/VimCommandRow"]
custom_minimum_size = Vector2(12, 0)
layout_mode = 2
text = ""

[node name="VimCommandEdit" type="LineEdit" parent="Root/VimCommandBar/VimCommandMargin/VimCommandRow"]
layout_mode = 2
size_flags_horizontal = 3
editable = false
virtual_keyboard_enabled = false
Loading
Loading