Skip to content

Commit 55dd7ad

Browse files
committedJun 2, 2023
feat: add ability to move cursor in search mode
- The default keymap of bubbles/textinput has been reset as some keys clash with the cursor navigation keys. We should support customization of the textinput keymap in the future.
1 parent 3cba5b8 commit 55dd7ad

File tree

6 files changed

+110
-12
lines changed

6 files changed

+110
-12
lines changed
 

‎README.md

+20-11
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ Prefixes are useful for applications with a common leading hotkey like tmux.
128128

129129
Refer to the `examples` for more examples.
130130

131-
>Multiline fields are not supported at the moment.
131+
>Multiline fields are not supported!
132132

133133
### Quick Add
134134

@@ -175,12 +175,15 @@ By default, the following options are included.
175175
| `padding` | `1` | Space between border and text |
176176
| `border` | `"hidden"` | Border style: `normal, rounded, double, thick, hidden`|
177177

178-
> If set, `XDG_CONFIG_HOME` will be used in Unix and Darwin systems. Otherwise, keyb
179-
> will fall back to the default OS config directory:
180-
>
181-
>- Unix: `$XDG_CONFIG_HOME/keyb/keyb.yml`,
182-
>- MacOS or Darwin: `$HOME/Library/Application Support/keyb/keyb.yml`,
183-
>- Windows: `%Appdata%\keyb\keyb.yml`
178+
#### keyb Configuration Path
179+
180+
If `XDG_CONFIG_HOME` is set, it will be prioritized and used in Unix and Darwin
181+
systems. Otherwise, keyb will fall back to the default OS config directory
182+
defined as such:
183+
184+
- Unix: `$XDG_CONFIG_HOME/keyb/keyb.yml`,
185+
- MacOS/Darwin: `$HOME/Library/Application Support/keyb/keyb.yml`,
186+
- Windows: `%Appdata%\keyb\keyb.yml`
184187

185188
#### Color
186189
Both ANSI and hex color codes are supported.
@@ -204,21 +207,27 @@ Multiple keys may be set for a single binding, separated by commas.
204207
| Hotkey | Default | Description |
205208
| ----------------------- | -------------------------- | ---------------- |
206209
| `up`, `down` | <kbd>j, k / Up, Down</kbd> | Move cursor |
207-
| `half_up, half_down` | <kbd>Ctrl + u, d</kbd> | Move half window |
208-
| `full_up, full_down` | <kbd>Ctrl + b, f</kbd> | Move full window |
210+
| `up_focus`, `down_focus`| <kbd>Ctrl + j, ctrl + k </kbd> | Move cursor in search mode |
211+
| `half_up, half_down` | <kbd>Ctrl + u, d</kbd> | Move half window (also works in search mode) |
212+
| `full_up, full_down` | <kbd>Ctrl + b, f</kbd> | Move full window (also works in search mode) |
209213
| `top, middle, bottom` | <kbd>H, M, L</kbd> | Go to top, middle, bottom of screen |
210214
| `first_line, last_line` | <kbd>g, G</kbd> | Go to first, last line |
211215
| `search` | <kbd>/</kbd> | Enter search mode |
212216
| `clear_search` | <kbd>Alt + d</kbd> | Clear current search (remains in search mode) |
213217
| `normal` | <kbd>Esc</kbd> | Exit search mode |
214218
| `quit` | <kbd>Ctrl + c, q</kbd> | Quit |
215219

220+
>The
221+
>[default](https://github.com/charmbracelet/bubbles/blob/afd7868712d4a4f817829dc7e1868c337c5e5cff/textinput/textinput.go#L61)
222+
>key bindings for the search bar have been reset temporarily. Customization of
223+
>these key bindings are coming soon.
224+
216225
## Roadmap
217226

218227
- [x] Ability to customize keyb hotkeys
219-
- [ ] Export to additional file formats (`json, toml, conf/ini` etc.)
220228
- [x] `a, add` subcommand to quickly add a single hotkey entry from the CLI
221-
- [ ] Automatic parsing from online cheatsheet repos (eg. `cheat/cheatsheets`)
229+
- [ ] Export to additional file formats (`json, toml, conf/ini` etc.)
230+
- [ ] Allow customization of search bar key bindings
222231

223232
## Contributing
224233

‎config/config.go

+4
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ type Keys struct {
5656
Quit string
5757
Up string
5858
Down string
59+
UpFocus string `yaml:"up_focus"`
60+
DownFocus string `yaml:"down_focus"`
5961
HalfUp string `yaml:"half_up"`
6062
HalfDown string `yaml:"half_down"`
6163
FullUp string `yaml:"full_up"`
@@ -188,6 +190,8 @@ func generateDefaultConfig() (*Config, error) {
188190
Quit: "q, ctrl+c",
189191
Up: "k, up",
190192
Down: "j, down",
193+
UpFocus: "ctrl+k",
194+
DownFocus: "ctrl+j",
191195
HalfUp: "ctrl+u",
192196
HalfDown: "ctrl+d",
193197
FullUp: "ctrl+b",

‎config/config_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ func TestParse(t *testing.T) {
7474
Quit: "q, ctrl+c",
7575
Up: "k, up",
7676
Down: "j, down",
77+
UpFocus: "ctrl+k",
78+
DownFocus: "ctrl+j",
7779
HalfUp: "ctrl+u",
7880
HalfDown: "ctrl+d",
7981
FullUp: "ctrl+b",

‎ui/list/keymap.go

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ type KeyMap struct {
1616
HalfDown key.Binding
1717
FullUp key.Binding
1818
FullDown key.Binding
19+
UpFocus key.Binding
20+
DownFocus key.Binding
1921
GoToFirstLine key.Binding
2022
GoToLastLine key.Binding
2123
GoToTop key.Binding
@@ -38,6 +40,8 @@ func CreateKeyMap(keys config.Keys) KeyMap {
3840
HalfDown: SetKey(keys.HalfDown),
3941
FullUp: SetKey(keys.FullUp),
4042
FullDown: SetKey(keys.FullDown),
43+
UpFocus: SetKey(keys.UpFocus),
44+
DownFocus: SetKey(keys.DownFocus),
4145
GoToFirstLine: SetKey(keys.GoToFirstLine),
4246
GoToLastLine: SetKey(keys.GoToLastLine),
4347
GoToTop: SetKey(keys.GoToTop),

‎ui/list/list.go

+8
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ func (m *Model) configure(c *config.Config) {
7373
Foreground(lipgloss.Color(c.PromptColor))
7474
m.searchBar.Placeholder = c.Placeholder
7575

76+
// temp measure: clear default keymap
77+
// TODO allow customization of text input keymap
78+
m.searchBar.KeyMap = textinput.KeyMap{}
79+
7680
if c.PlaceholderFg != "" || c.PlaceholderBg != "" {
7781
m.searchBar.PlaceholderStyle = lipgloss.NewStyle().
7882
Foreground(lipgloss.Color(c.PlaceholderFg)).
@@ -171,6 +175,10 @@ func (m *Model) SyncContent(table *table.Model) {
171175
return
172176
}
173177

178+
if m.cursor > table.LineCount {
179+
m.cursor = table.LineCount - 1
180+
}
181+
174182
for i, row := range table.Rows {
175183
if i == m.cursor {
176184
row.IsSelected = true

‎ui/list/update.go

+72-1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,17 @@ func (m *Model) handleNormal(msg tea.Msg) tea.Cmd {
9393
m.viewport.LineDown(1)
9494
}
9595

96+
case key.Matches(msg, m.keys.UpFocus):
97+
m.cursor--
98+
if m.cursorPastViewTop() {
99+
m.viewport.LineUp(1)
100+
}
101+
case key.Matches(msg, m.keys.DownFocus):
102+
m.cursor++
103+
if m.cursorPastViewBottom() {
104+
m.viewport.LineDown(1)
105+
}
106+
96107
case key.Matches(msg, m.keys.HalfUp):
97108
m.cursor -= m.viewport.Height / 2
98109
if m.cursorPastViewTop() {
@@ -186,6 +197,67 @@ func (m *Model) handleSearch(msg tea.Msg) tea.Cmd {
186197
m.searchBar.Reset()
187198
return m.startSearch()
188199

200+
// scrolling in search mode
201+
case key.Matches(msg, m.keys.UpFocus):
202+
m.cursor--
203+
if m.cursorPastViewTop() {
204+
m.viewport.LineUp(1)
205+
}
206+
return nil
207+
case key.Matches(msg, m.keys.DownFocus):
208+
m.cursor++
209+
if m.cursorPastViewBottom() {
210+
m.viewport.LineDown(1)
211+
}
212+
return nil
213+
214+
case key.Matches(msg, m.keys.HalfUp):
215+
m.cursor -= m.viewport.Height / 2
216+
if m.cursorPastViewTop() {
217+
m.viewport.HalfViewUp()
218+
}
219+
220+
// don't loop around
221+
if m.cursorPastBeginning() {
222+
m.cursorToBeginning()
223+
m.viewport.GotoTop()
224+
}
225+
case key.Matches(msg, m.keys.HalfDown):
226+
m.cursor += m.viewport.Height / 2
227+
if m.cursorPastViewBottom() {
228+
m.viewport.HalfViewDown()
229+
}
230+
231+
// don't loop around
232+
if m.cursorPastEnd() {
233+
m.cursorToEnd()
234+
m.viewport.GotoBottom()
235+
}
236+
237+
case key.Matches(msg, m.keys.FullUp):
238+
m.cursor -= m.viewport.Height
239+
if m.cursorPastViewTop() {
240+
m.viewport.ViewUp()
241+
}
242+
243+
// don't loop around
244+
if m.cursorPastBeginning() {
245+
m.cursorToBeginning()
246+
m.viewport.GotoTop()
247+
}
248+
249+
case key.Matches(msg, m.keys.FullDown):
250+
m.cursor += m.viewport.Height
251+
if m.cursorPastViewBottom() {
252+
m.viewport.ViewDown()
253+
}
254+
255+
// don't loop around
256+
if m.cursorPastEnd() {
257+
m.cursorToEnd()
258+
m.viewport.GotoBottom()
259+
}
260+
189261
case key.Matches(msg, m.keys.Normal):
190262
m.search = false
191263
m.searchBar.Blur()
@@ -207,7 +279,6 @@ func (m *Model) handleSearch(msg tea.Msg) tea.Cmd {
207279
} else {
208280
matchRows(m)
209281
}
210-
m.cursorToBeginning()
211282

212283
// reset if search input is empty regardless of filterState
213284
if m.searchBar.Value() == "" {

0 commit comments

Comments
 (0)
Please sign in to comment.