feat(desktop): url focus and mru tab shortcuts (#5683)

Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com>
This commit is contained in:
Shreyas 2025-12-16 17:25:56 +05:30 committed by GitHub
parent 745fc9d1f6
commit 824dce79d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 508 additions and 6 deletions

View file

@ -1412,7 +1412,8 @@
"send_request": "Send Request", "send_request": "Send Request",
"share_request": "Share Request", "share_request": "Share Request",
"show_code": "Generate code snippet", "show_code": "Generate code snippet",
"title": "Request" "title": "Request",
"focus_url": "Focus URL bar"
}, },
"response": { "response": {
"copy": "Copy response to clipboard", "copy": "Copy response to clipboard",
@ -1427,7 +1428,9 @@
"next_tab": "Next Tab", "next_tab": "Next Tab",
"previous_tab": "Previous Tab", "previous_tab": "Previous Tab",
"first_tab": "Switch to First Tab", "first_tab": "Switch to First Tab",
"last_tab": "Switch to Last Tab" "last_tab": "Switch to Last Tab",
"mru_switch": "Switch to recent tab (MRU)",
"mru_switch_reverse": "Switch to previous recent tab (MRU)"
}, },
"theme": { "theme": {
"black": "Switch theme to Black Mode", "black": "Switch theme to Black Mode",
@ -1524,6 +1527,8 @@
"previous": "Switch to previous tab", "previous": "Switch to previous tab",
"switch_to_first": "Switch to first tab", "switch_to_first": "Switch to first tab",
"switch_to_last": "Switch to last tab", "switch_to_last": "Switch to last tab",
"mru_switch": "Switch to recent tab",
"mru_switch_reverse": "Switch to previous recent tab",
"title": "Tabs" "title": "Tabs"
}, },
"workspace": { "workspace": {

View file

@ -56,6 +56,7 @@
class="flex flex-1 whitespace-nowrap rounded-r border-l border-divider bg-primaryLight transition" class="flex flex-1 whitespace-nowrap rounded-r border-l border-divider bg-primaryLight transition"
> >
<SmartEnvInput <SmartEnvInput
ref="urlInput"
v-model="tab.document.request.endpoint" v-model="tab.document.request.endpoint"
:placeholder="`${t('request.url_placeholder')}`" :placeholder="`${t('request.url_placeholder')}`"
:auto-complete-source="userHistories" :auto-complete-source="userHistories"
@ -322,6 +323,7 @@ const show = ref<any | null>(null)
const clearAll = ref<any | null>(null) const clearAll = ref<any | null>(null)
const copyRequestAction = ref<any | null>(null) const copyRequestAction = ref<any | null>(null)
const saveRequestAction = ref<any | null>(null) const saveRequestAction = ref<any | null>(null)
const urlInput = ref<{ focus: () => void } | null>(null)
const history = useReadonlyStream<RESTHistoryEntry[]>(restHistory$, []) const history = useReadonlyStream<RESTHistoryEntry[]>(restHistory$, [])
@ -667,6 +669,10 @@ defineActionHandler("request.show-code", () => {
showCodegenModal.value = true showCodegenModal.value = true
}) })
defineActionHandler("request.focus-url", () => {
urlInput.value?.focus()
})
const isCustomMethod = computed(() => { const isCustomMethod = computed(() => {
return ( return (
tab.value.document.request.method === "CUSTOM" || tab.value.document.request.method === "CUSTOM" ||

View file

@ -658,6 +658,18 @@ const triggerTextSelection = () => {
}) })
}) })
} }
/**
* Focuses the input editor
*/
const focusInput = () => {
view.value?.focus()
}
defineExpose({
focus: focusInput,
})
onMounted(() => { onMounted(() => {
if (editor.value) { if (editor.value) {
if (!view.value) initView(editor.value) if (!view.value) initView(editor.value)

View file

@ -70,6 +70,9 @@ export type HoppAction =
| "tab.switch-to-first" // Switch to first tab | "tab.switch-to-first" // Switch to first tab
| "tab.switch-to-last" // Switch to last tab | "tab.switch-to-last" // Switch to last tab
| "tab.reopen-closed" // Reopen recently closed tab | "tab.reopen-closed" // Reopen recently closed tab
| "tab.mru-switch" // Switch to MRU tab (Ctrl/Cmd+Alt+])
| "tab.mru-switch-reverse" // Switch to previous MRU tab (Ctrl/Cmd+Alt+[)
| "request.focus-url" // Focus the URL bar
| "collection.new" // Create root collection | "collection.new" // Create root collection
| "flyouts.chat.open" // Shows the keybinds flyout | "flyouts.chat.open" // Shows the keybinds flyout
| "flyouts.keybinds.toggle" // Shows the keybinds flyout | "flyouts.keybinds.toggle" // Shows the keybinds flyout

View file

@ -44,6 +44,7 @@ type Key =
| "u" | "v" | "w" | "x" | "y" | "z" | "0" | "1" | "2" | "3" | "u" | "v" | "w" | "x" | "y" | "z" | "0" | "1" | "2" | "3"
| "4" | "5" | "6" | "7" | "8" | "9" | "up" | "down" | "left" | "4" | "5" | "6" | "7" | "8" | "9" | "up" | "down" | "left"
| "right" | "/" | "?" | "." | "enter" | "tab" | "delete" | "backspace" | "right" | "/" | "?" | "." | "enter" | "tab" | "delete" | "backspace"
| "[" | "]"
/* eslint-enable */ /* eslint-enable */
type ModifierBasedShortcutKey = `${ModifierKeys}-${Key}` type ModifierBasedShortcutKey = `${ModifierKeys}-${Key}`
@ -108,6 +109,9 @@ const desktopBindings: {
"ctrl-alt-0": "tab.switch-to-last", "ctrl-alt-0": "tab.switch-to-last",
"ctrl-alt-9": "tab.switch-to-first", "ctrl-alt-9": "tab.switch-to-first",
"ctrl-q": "app.quit", "ctrl-q": "app.quit",
"ctrl-alt-u": "request.focus-url",
"ctrl-alt-]": "tab.mru-switch",
"ctrl-alt-[": "tab.mru-switch-reverse",
} }
/** /**
@ -314,6 +318,8 @@ function getPressedKey(ev: KeyboardEvent): Key | null {
// Check if slash, period or enter // Check if slash, period or enter
if (key === "/" || key === "." || key === "enter") return key if (key === "/" || key === "." || key === "enter") return key
if (key === "[" || key === "]") return key
// If no other cases match, this is not a valid key // If no other cases match, this is not a valid key
return null return null
} }

View file

@ -174,6 +174,11 @@ export function getShortcuts(t: (x: string) => string): ShortcutDef[] {
// Desktop-only shortcuts // Desktop-only shortcuts
const desktopShortcuts: ShortcutDef[] = [ const desktopShortcuts: ShortcutDef[] = [
{
keys: [getPlatformSpecialKey(), getPlatformAlternateKey(), "U"],
label: t("shortcut.request.focus_url"),
section: t("shortcut.request.title"),
},
{ {
keys: [getPlatformSpecialKey(), "T"], keys: [getPlatformSpecialKey(), "T"],
label: t("shortcut.tabs.new_tab"), label: t("shortcut.tabs.new_tab"),
@ -204,6 +209,16 @@ export function getShortcuts(t: (x: string) => string): ShortcutDef[] {
label: t("shortcut.tabs.last_tab"), label: t("shortcut.tabs.last_tab"),
section: t("shortcut.tabs.title"), section: t("shortcut.tabs.title"),
}, },
{
keys: [getPlatformSpecialKey(), getPlatformAlternateKey(), "]"],
label: t("shortcut.tabs.mru_switch"),
section: t("shortcut.tabs.title"),
},
{
keys: [getPlatformSpecialKey(), getPlatformAlternateKey(), "["],
label: t("shortcut.tabs.mru_switch_reverse"),
section: t("shortcut.tabs.title"),
},
] ]
// Return base shortcuts + platform-specific shortcuts // Return base shortcuts + platform-specific shortcuts

View file

@ -270,4 +270,12 @@ defineActionHandler("tab.switch-to-last", () => {
defineActionHandler("tab.reopen-closed", () => { defineActionHandler("tab.reopen-closed", () => {
tabs.reopenClosedTab() tabs.reopenClosedTab()
}) })
defineActionHandler("tab.mru-switch", () => {
tabs.goToMRUTab()
})
defineActionHandler("tab.mru-switch-reverse", () => {
tabs.goToPreviousMRUTab()
})
</script> </script>

View file

@ -448,6 +448,14 @@ defineActionHandler("tab.reopen-closed", () => {
tabs.reopenClosedTab() tabs.reopenClosedTab()
}) })
defineActionHandler("tab.mru-switch", () => {
tabs.goToMRUTab()
})
defineActionHandler("tab.mru-switch-reverse", () => {
tabs.goToPreviousMRUTab()
})
useService(RequestInspectorService) useService(RequestInspectorService)
useService(EnvironmentInspectorService) useService(EnvironmentInspectorService)
useService(ResponseInspectorService) useService(ResponseInspectorService)

View file

@ -146,6 +146,31 @@ export class TabSpotlightSearcherService extends StaticSpotlightSearcherService<
this.isOnlyTab.value this.isOnlyTab.value
), ),
}, },
tab_mru_switch: {
text: [this.t("spotlight.tab.title"), this.t("spotlight.tab.mru_switch")],
alternates: ["tab", "recent", "mru", "switch"],
icon: markRaw(IconArrowRight),
excludeFromSearch: computed(
() =>
!this.showAction.value ||
!this.isDesktopMode.value ||
this.isOnlyTab.value
),
},
tab_mru_switch_reverse: {
text: [
this.t("spotlight.tab.title"),
this.t("spotlight.tab.mru_switch_reverse"),
],
alternates: ["tab", "recent", "mru", "previous", "switch"],
icon: markRaw(IconArrowLeft),
excludeFromSearch: computed(
() =>
!this.showAction.value ||
!this.isDesktopMode.value ||
this.isOnlyTab.value
),
},
}) })
// TODO: Constructors are no longer recommended as of dioc > 3, use onServiceInit instead // TODO: Constructors are no longer recommended as of dioc > 3, use onServiceInit instead
@ -184,5 +209,7 @@ export class TabSpotlightSearcherService extends StaticSpotlightSearcherService<
if (id === "tab_next") invokeAction("tab.next") if (id === "tab_next") invokeAction("tab.next")
if (id === "tab_switch_to_first") invokeAction("tab.switch-to-first") if (id === "tab_switch_to_first") invokeAction("tab.switch-to-first")
if (id === "tab_switch_to_last") invokeAction("tab.switch-to-last") if (id === "tab_switch_to_last") invokeAction("tab.switch-to-last")
if (id === "tab_mru_switch") invokeAction("tab.mru-switch")
if (id === "tab_mru_switch_reverse") invokeAction("tab.mru-switch-reverse")
} }
} }

View file

@ -23,6 +23,14 @@ class MockTabService extends TabService<{ request: string }> {
this.watchCurrentTabID() this.watchCurrentTabID()
} }
public getMRUOrder(): string[] {
return [...this.mruOrder]
}
public getMRUNavigationIndex(): number {
return this.mruNavigationIndex
}
} }
describe("TabService", () => { describe("TabService", () => {
@ -341,4 +349,239 @@ describe("TabService", () => {
]) ])
}) })
}) })
describe("MRU Tab Navigation", () => {
it("should track MRU order when switching tabs", () => {
const container = new TestContainer()
const service = container.bind(MockTabService)
const tab2 = service.createNewTab({ request: "second request" })
const tab3 = service.createNewTab({ request: "third request" })
expect(service.getMRUOrder()[0]).toEqual(tab3.id)
service.setActiveTab("test")
expect(service.getMRUOrder()[0]).toEqual("test")
expect(service.getMRUOrder()[1]).toEqual(tab3.id)
expect(service.getMRUOrder()[2]).toEqual(tab2.id)
service.setActiveTab(tab2.id)
expect(service.getMRUOrder()[0]).toEqual(tab2.id)
expect(service.getMRUOrder()[1]).toEqual("test")
expect(service.getMRUOrder()[2]).toEqual(tab3.id)
})
it("should navigate forward through MRU list with goToMRUTab", () => {
const container = new TestContainer()
const service = container.bind(MockTabService)
const tab2 = service.createNewTab({ request: "second request" })
const tab3 = service.createNewTab({ request: "third request" })
service.setActiveTab("test")
service.setActiveTab(tab2.id)
service.setActiveTab(tab3.id)
expect(service.getActiveTab()?.id).toEqual(tab3.id)
service.goToMRUTab()
expect(service.getActiveTab()?.id).toEqual(tab2.id)
service.goToMRUTab()
expect(service.getActiveTab()?.id).toEqual("test")
service.goToMRUTab()
expect(service.getActiveTab()?.id).toEqual(tab3.id)
})
it("should navigate backward through MRU list with goToPreviousMRUTab", () => {
const container = new TestContainer()
const service = container.bind(MockTabService)
const tab2 = service.createNewTab({ request: "second request" })
const tab3 = service.createNewTab({ request: "third request" })
service.setActiveTab("test")
service.setActiveTab(tab2.id)
service.setActiveTab(tab3.id)
expect(service.getActiveTab()?.id).toEqual(tab3.id)
service.goToPreviousMRUTab()
expect(service.getActiveTab()?.id).toEqual("test")
service.goToPreviousMRUTab()
expect(service.getActiveTab()?.id).toEqual(tab2.id)
service.goToPreviousMRUTab()
expect(service.getActiveTab()?.id).toEqual(tab3.id)
})
it("should allow bidirectional MRU navigation", () => {
const container = new TestContainer()
const service = container.bind(MockTabService)
const tab2 = service.createNewTab({ request: "second request" })
const tab3 = service.createNewTab({ request: "third request" })
service.setActiveTab("test")
service.setActiveTab(tab2.id)
service.setActiveTab(tab3.id)
service.goToMRUTab() // -> tab2
service.goToMRUTab() // -> test
expect(service.getActiveTab()?.id).toEqual("test")
service.goToPreviousMRUTab() // -> tab2
expect(service.getActiveTab()?.id).toEqual(tab2.id)
service.goToMRUTab() // -> test
expect(service.getActiveTab()?.id).toEqual("test")
})
it("should handle MRU navigation with single tab", () => {
const container = new TestContainer()
const service = container.bind(MockTabService)
const originalActiveTab = service.getActiveTab()
service.goToMRUTab()
expect(service.getActiveTab()?.id).toEqual(originalActiveTab?.id)
service.goToPreviousMRUTab()
expect(service.getActiveTab()?.id).toEqual(originalActiveTab?.id)
})
it("should remove closed tabs from MRU order", () => {
const container = new TestContainer()
const service = container.bind(MockTabService)
const tab2 = service.createNewTab({ request: "second request" })
const tab3 = service.createNewTab({ request: "third request" })
service.setActiveTab("test")
service.setActiveTab(tab2.id)
service.setActiveTab(tab3.id)
expect(service.getMRUOrder()).toContain(tab2.id)
service.closeTab(tab2.id)
expect(service.getMRUOrder()).not.toContain(tab2.id)
expect(service.getMRUOrder().length).toEqual(2)
})
it("should reset MRU navigation index when tab is explicitly activated", () => {
const container = new TestContainer()
const service = container.bind(MockTabService)
const tab2 = service.createNewTab({ request: "second request" })
const tab3 = service.createNewTab({ request: "third request" })
service.setActiveTab("test")
service.setActiveTab(tab2.id)
service.setActiveTab(tab3.id)
service.goToMRUTab()
expect(service.getMRUNavigationIndex()).toEqual(1)
service.setActiveTab("test")
expect(service.getMRUNavigationIndex()).toEqual(-1)
})
it("should commit MRU navigation and update order", () => {
const container = new TestContainer()
const service = container.bind(MockTabService)
const tab2 = service.createNewTab({ request: "second request" })
const tab3 = service.createNewTab({ request: "third request" })
service.setActiveTab("test")
service.setActiveTab(tab2.id)
service.setActiveTab(tab3.id)
service.goToMRUTab()
expect(service.getActiveTab()?.id).toEqual(tab2.id)
expect(service.getMRUNavigationIndex()).toEqual(1)
service.commitMRUNavigation()
expect(service.getMRUOrder()[0]).toEqual(tab2.id)
expect(service.getMRUNavigationIndex()).toEqual(-1)
})
it("should reset MRU navigation without committing", () => {
const container = new TestContainer()
const service = container.bind(MockTabService)
service.createNewTab({ request: "second request" })
service.createNewTab({ request: "third request" })
service.goToMRUTab()
expect(service.getMRUNavigationIndex()).toBeGreaterThan(-1)
service.resetMRUNavigation()
expect(service.getMRUNavigationIndex()).toEqual(-1)
})
it("should not add background tabs to MRU until activated", () => {
const container = new TestContainer()
const service = container.bind(MockTabService)
const backgroundTab = service.createNewTab(
{ request: "background request" },
false
)
// (it will be added when first activated)
const mruBeforeActivation = service.getMRUOrder()
expect(mruBeforeActivation).not.toContain(backgroundTab.id)
expect(mruBeforeActivation[0]).toEqual("test")
expect(mruBeforeActivation.length).toEqual(1)
service.setActiveTab(backgroundTab.id)
expect(service.getMRUOrder()[0]).toEqual(backgroundTab.id)
expect(service.getMRUOrder().length).toEqual(2)
})
it("should initialize MRU order from persisted state", () => {
const container = new TestContainer()
const service = container.bind(MockTabService)
const persistedState = {
lastActiveTabID: "persisted-tab-2",
orderedDocs: [
{ tabID: "persisted-tab-1", doc: { request: "request 1" } },
{ tabID: "persisted-tab-2", doc: { request: "request 2" } },
{ tabID: "persisted-tab-3", doc: { request: "request 3" } },
],
}
service.loadTabsFromPersistedState(persistedState)
expect(service.getMRUOrder().length).toEqual(3)
expect(service.getMRUOrder()[0]).toEqual("persisted-tab-2")
})
it("should handle closeOtherTabs correctly with MRU", () => {
const container = new TestContainer()
const service = container.bind(MockTabService)
const tab2 = service.createNewTab({ request: "second request" })
service.createNewTab({ request: "third request" })
service.closeOtherTabs(tab2.id)
expect(service.getMRUOrder()).toEqual([tab2.id])
expect(service.getMRUNavigationIndex()).toEqual(-1)
})
})
}) })

View file

@ -132,6 +132,29 @@ export interface TabService<Doc> {
*/ */
reopenClosedTab(): boolean reopenClosedTab(): boolean
/**
* Navigates forward through the MRU list (to older tabs).
* Each call moves one step forward in the MRU history.
*/
goToMRUTab(): void
/**
* Navigates backward through the MRU list (to more recent tabs).
* Each call moves one step backward in the MRU history.
*/
goToPreviousMRUTab(): void
/**
* Commits the current MRU navigation selection.
* Should be called when the modifier key is released to finalize the tab switch.
*/
commitMRUNavigation(): void
/**
* Resets MRU navigation state without committing.
*/
resetMRUNavigation(): void
/** /**
* Gets a computed reference to a persistable tab state. * Gets a computed reference to a persistable tab state.
* @returns A computed reference to a persistable tab state object. * @returns A computed reference to a persistable tab state object.

View file

@ -28,6 +28,13 @@ export abstract class TabService<Doc>
protected recentlyClosedTabs: Array<{ tab: HoppTab<Doc>; index: number }> = [] protected recentlyClosedTabs: Array<{ tab: HoppTab<Doc>; index: number }> = []
protected readonly MAX_CLOSED_TABS_HISTORY = 10 protected readonly MAX_CLOSED_TABS_HISTORY = 10
// MRU (Most Recently Used) tracking
// mruOrder[0] is the most recently used tab, mruOrder[n-1] is the least recently used
protected mruOrder: string[] = ["test"]
// Navigation index for cycling through MRU list while modifier key is held
// -1 means not currently navigating, 0+ means current position in mruOrder
protected mruNavigationIndex: number = -1
public currentTabID = refWithControl("test", { public currentTabID = refWithControl("test", {
onBeforeChange: (newTabID) => { onBeforeChange: (newTabID) => {
if (!newTabID || !this.tabMap.has(newTabID)) { if (!newTabID || !this.tabMap.has(newTabID)) {
@ -80,6 +87,8 @@ export abstract class TabService<Doc>
if (switchToIt) { if (switchToIt) {
this.setActiveTab(id) this.setActiveTab(id)
} }
// Note: We don't add to mruOrder here if switchToIt is false
// The tab will be added to mruOrder when it's first activated via setActiveTab
return tab return tab
} }
@ -94,12 +103,27 @@ export abstract class TabService<Doc>
public setActiveTab(tabID: string): void { public setActiveTab(tabID: string): void {
this.currentTabID.value = tabID this.currentTabID.value = tabID
this.updateMRUOrder(tabID)
}
private updateMRUOrder(tabID: string): void {
const index = this.mruOrder.indexOf(tabID)
if (index > -1) {
this.mruOrder.splice(index, 1)
}
this.mruOrder.unshift(tabID)
// Reset navigation index when a tab is explicitly activated
// This ensures the next MRU navigation starts fresh
this.mruNavigationIndex = -1
} }
public loadTabsFromPersistedState(data: PersistableTabState<Doc>): void { public loadTabsFromPersistedState(data: PersistableTabState<Doc>): void {
if (data) { if (data) {
this.tabMap.clear() this.tabMap.clear()
this.tabOrdering.value = [] this.tabOrdering.value = []
this.mruOrder = []
this.mruNavigationIndex = -1
for (const doc of data.orderedDocs) { for (const doc of data.orderedDocs) {
this.tabMap.set(doc.tabID, { this.tabMap.set(doc.tabID, {
@ -108,6 +132,7 @@ export abstract class TabService<Doc>
}) })
this.tabOrdering.value.push(doc.tabID) this.tabOrdering.value.push(doc.tabID)
this.mruOrder.push(doc.tabID)
} }
this.setActiveTab(data.lastActiveTabID) this.setActiveTab(data.lastActiveTabID)
@ -175,6 +200,14 @@ export abstract class TabService<Doc>
this.addToRecentlyClosedTabs(tab, tabIndex) this.addToRecentlyClosedTabs(tab, tabIndex)
const mruIndex = this.mruOrder.indexOf(tabID)
if (mruIndex > -1) {
this.mruOrder.splice(mruIndex, 1)
}
// Reset navigation index when tabs change
this.mruNavigationIndex = -1
this.tabOrdering.value.splice(tabIndex, 1) this.tabOrdering.value.splice(tabIndex, 1)
nextTick(() => { nextTick(() => {
@ -191,6 +224,8 @@ export abstract class TabService<Doc>
} }
this.tabOrdering.value = [tabID] this.tabOrdering.value = [tabID]
this.mruOrder = [tabID]
this.mruNavigationIndex = -1
this.tabMap.forEach((_, id) => { this.tabMap.forEach((_, id) => {
if (id !== tabID) this.tabMap.delete(id) if (id !== tabID) this.tabMap.delete(id)
@ -249,6 +284,94 @@ export abstract class TabService<Doc>
return true return true
} }
/**
* Navigates forward through the MRU list (to older tabs).
* Each call moves one step forward in the MRU history.
*
* Behavior:
* - First call: moves from current tab (index 0) to second most recent (index 1)
* - Subsequent calls: continue moving forward through history
* - Wraps around to the beginning when reaching the end
*/
public goToMRUTab(): void {
// Clean up any stale entries
this.mruOrder = this.mruOrder.filter((tabID) => this.tabMap.has(tabID))
if (this.mruOrder.length <= 1) return
// If not currently navigating, start from position 0 (current tab)
if (this.mruNavigationIndex === -1) {
this.mruNavigationIndex = 0
}
// Move forward in MRU list (toward older tabs)
this.mruNavigationIndex =
(this.mruNavigationIndex + 1) % this.mruOrder.length
const targetTabID = this.mruOrder[this.mruNavigationIndex]
if (targetTabID) {
// Use currentTabID directly to avoid resetting navigation state
this.currentTabID.value = targetTabID
}
}
/**
* Navigates backward through the MRU list (to more recent tabs).
* Each call moves one step backward in the MRU history.
*
* Behavior:
* - Moves backward from current position in MRU navigation
* - Wraps around to the end when reaching the beginning
*/
public goToPreviousMRUTab(): void {
// Clean up any stale entries
this.mruOrder = this.mruOrder.filter((tabID) => this.tabMap.has(tabID))
if (this.mruOrder.length <= 1) return
// If not currently navigating, start from position 0 (current tab)
if (this.mruNavigationIndex === -1) {
this.mruNavigationIndex = 0
}
// Move backward in MRU list (toward more recent tabs)
this.mruNavigationIndex =
this.mruNavigationIndex === 0
? this.mruOrder.length - 1
: this.mruNavigationIndex - 1
const targetTabID = this.mruOrder[this.mruNavigationIndex]
if (targetTabID) {
// Use currentTabID directly to avoid resetting navigation state
this.currentTabID.value = targetTabID
}
}
/**
* Commits the current MRU navigation selection.
* Should be called when the modifier key is released to finalize the tab switch.
* This moves the selected tab to the front of the MRU order.
*/
public commitMRUNavigation(): void {
if (this.mruNavigationIndex > 0) {
const selectedTabID = this.mruOrder[this.mruNavigationIndex]
if (selectedTabID) {
// Move the selected tab to the front of MRU order
this.mruOrder.splice(this.mruNavigationIndex, 1)
this.mruOrder.unshift(selectedTabID)
}
}
this.mruNavigationIndex = -1
}
/**
* Resets MRU navigation state without committing.
* Useful if navigation is cancelled.
*/
public resetMRUNavigation(): void {
this.mruNavigationIndex = -1
}
private addToRecentlyClosedTabs(tab: HoppTab<Doc>, index: number): void { private addToRecentlyClosedTabs(tab: HoppTab<Doc>, index: number): void {
this.recentlyClosedTabs.push({ tab, index }) this.recentlyClosedTabs.push({ tab, index })

View file

@ -18,6 +18,7 @@ manifest.json
src-tauri/hopp_bundle.zip src-tauri/hopp_bundle.zip
src-tauri/hopp_manifest.json src-tauri/hopp_manifest.json
src-tauri/hoppscotch-desktop-data src-tauri/hoppscotch-desktop-data
target
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*

View file

@ -368,14 +368,36 @@ async function initApp() {
} else if ( } else if (
isCtrlOrCmd && isCtrlOrCmd &&
!e.shiftKey && !e.shiftKey &&
!e.altKey && e.altKey &&
e.key.toLowerCase() === "l" e.code === "KeyU"
) { ) {
// Ctrl/Cmd + L - Focus Address Bar // Ctrl/Cmd + Alt + U - Focus URL Bar
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
e.stopImmediatePropagation() e.stopImmediatePropagation()
shortcutEvent = "focus-url" shortcutEvent = "ctrl-alt-u"
} else if (
isCtrlOrCmd &&
!e.shiftKey &&
e.altKey &&
e.code === "BracketRight"
) {
// Ctrl/Cmd + Alt + ] - MRU Tab Switch
e.preventDefault()
e.stopPropagation()
e.stopImmediatePropagation()
shortcutEvent = "ctrl-alt-]"
} else if (
isCtrlOrCmd &&
!e.shiftKey &&
e.altKey &&
e.code === "BracketLeft"
) {
// Ctrl/Cmd + Alt + [ - MRU Tab Switch (Reverse)
e.preventDefault()
e.stopPropagation()
e.stopImmediatePropagation()
shortcutEvent = "ctrl-alt-["
} }
if (shortcutEvent) { if (shortcutEvent) {