d93d3d534fe8

let Esc always step back and leave the view from the list, dropping a kept filter first, and stop unknown escape sequences counting as Esc (3.0.69)

time
2026-09-26 13:35 UTC · 2 weeks ago
machine
Junes-MacBook-Pro.local
agent
claude-opus-5-5
session
history view
parent
4806e6424ec3
6 files VERSION+1 −1src/view/term.v+3 −1src/view/term_test.v+9 −0src/view/view.v+9 −1src/view/view_test.v+26 −0v.mod+1 −1
VERSION+1 −1
@@ -1 +1 @@1-3.0.681+3.0.69

view file

src/view/term.v+3 −1
@@ -167,9 +167,11 @@167167 					kind: .page_down168168 				}169169 			}170+			// Shift-tab, function keys, focus reports: not Esc, which would171+			// leave the view.170172 			else {171173 				Key{172-					kind: .escape174+					kind: .unknown173175 				}174176 			}175177 		}

view file

src/view/term_test.v+9 −0
@@ -27,3 +27,12 @@2727 	assert decode('search'.bytes()).text == 'search'2828 	assert decode('✦'.bytes()).text == '✦'2929 }30+31+// Now that Esc leaves the view, only a lone Esc may count as one: shift-tab,32+// function keys and focus reports must not close it.33+fn test_unknown_escape_sequences_are_not_esc() {34+	assert decode([u8(0x1b), u8(`[`), u8(`Z`)]).kind == .unknown35+	assert decode([u8(0x1b), u8(`O`), u8(`P`)]).kind == .unknown36+	assert decode([u8(0x1b), u8(`[`), u8(`I`)]).kind == .unknown37+	assert decode([u8(0x1b), u8(`x`)]).kind == .unknown38+}

view file

src/view/view.v+9 −1
@@ -168,6 +168,14 @@168168 				session.mode = .detail169169 			}170170 		}171+		// Esc goes back one step: a kept search filter first, then the view.172+		.escape {173+			if session.screen.query == '' {174+				return true175+			}176+			session.screen.query = ''177+			session.screen.selected = 0178+		}171179 		.rune {172180 			match key.text {173181 				'q' {@@ -531,7 +539,7 @@531539 			'${action_word(session.pending.kind)} [${session.pending.save_id}]? y yes · n/esc cancel'532540 		}533541 		.list {534-			'↑↓ nav · / search · tab mode · c copy · enter detail · q'542+			'↑↓ nav · / search · tab mode · c copy · enter detail · q/esc'535543 		}536544 	}537545 	left := ' view: ${tab_name(session.screen.tab)} · ${hint}'

view file

src/view/view_test.v+26 −0
@@ -317,3 +317,29 @@317317 	// The last block ends at the bottom of the body, right above the status bar.318318 	assert lines.len - 2 - separators.last() <= 1319319 }320+321+// Esc always goes back one step, and from the list it leaves the view: first322+// it drops a kept search filter, then it exits, just as q does.323+fn test_esc_goes_back_or_exits_from_the_list() {324+	mut session := list_session([save('aaa', 'first', false)])325+	session.screen.query = 'fir'326+	assert !handle_key(mut session, unsafe { nil }, Key{ kind: .escape }, never_act)327+	assert session.screen.query == ''328+	assert handle_key(mut session, unsafe { nil }, Key{ kind: .escape }, never_act)329+}330+331+fn test_esc_steps_back_from_every_other_mode() {332+	mut session := list_session([save('aaa', 'first', false)])333+	handle_key(mut session, unsafe { nil }, Key{ kind: .enter }, never_act)334+	handle_key(mut session, unsafe { nil }, Key{ kind: .rune, text: 'f' }, never_act)335+	assert session.mode == .reason336+	handle_key(mut session, unsafe { nil }, Key{ kind: .escape }, never_act)337+	assert session.mode == .detail338+	handle_key(mut session, unsafe { nil }, Key{ kind: .rune, text: 'l' }, never_act)339+	assert session.mode == .confirm340+	handle_key(mut session, unsafe { nil }, Key{ kind: .escape }, never_act)341+	assert session.mode == .list342+	handle_key(mut session, unsafe { nil }, Key{ kind: .enter }, never_act)343+	handle_key(mut session, unsafe { nil }, Key{ kind: .escape }, never_act)344+	assert session.mode == .list345+}

view file

v.mod+1 −1
@@ -1,6 +1,6 @@11 Module {22 	name: 'koh3'3-	version: '3.0.68'3+	version: '3.0.69'44 	description: 'One SQLite file per repo, one line of history.'55 	license: 'MIT'66 }

view file