feat(v0.2): tab title = current folder (from OSC title)
- title-util.ts: folderFromTitle() parses 'user@host:~/path' → last folder segment (home stays ~); pure, dependency-free, 7 unit tests - terminal-session.ts: onTitleChange → folderFromTitle so the tab auto-names to the cwd folder and updates live on cd - Browser-verified: ~ → cd project → 'web-terminal' → cd docs → 'docs' → cd ~ → '~'. 198 tests green, typecheck + build ok.
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
import { Terminal } from '@xterm/xterm'
|
import { Terminal } from '@xterm/xterm'
|
||||||
import { FitAddon } from '@xterm/addon-fit'
|
import { FitAddon } from '@xterm/addon-fit'
|
||||||
import type { ClientMessage, ServerMessage } from '../src/types.js'
|
import type { ClientMessage, ServerMessage } from '../src/types.js'
|
||||||
|
import { folderFromTitle } from './title-util.js'
|
||||||
|
|
||||||
const RESET = '\x1b[0m'
|
const RESET = '\x1b[0m'
|
||||||
const BOLD = '\x1b[1m'
|
const BOLD = '\x1b[1m'
|
||||||
@@ -94,7 +95,8 @@ export class TerminalSession {
|
|||||||
this.term.open(this.el)
|
this.term.open(this.el)
|
||||||
|
|
||||||
this.term.onData((data) => this.send(data))
|
this.term.onData((data) => this.send(data))
|
||||||
this.term.onTitleChange((title) => this.onTitle?.(title))
|
// Tab title = current folder, derived from the shell's OSC title.
|
||||||
|
this.term.onTitleChange((title) => this.onTitle?.(folderFromTitle(title) ?? title))
|
||||||
|
|
||||||
this.resizeObserver = new ResizeObserver(() => this.scheduleResize())
|
this.resizeObserver = new ResizeObserver(() => this.scheduleResize())
|
||||||
this.resizeObserver.observe(this.el)
|
this.resizeObserver.observe(this.el)
|
||||||
|
|||||||
25
public/title-util.ts
Normal file
25
public/title-util.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* public/title-util.ts — derive a short tab title from a terminal OSC title.
|
||||||
|
*
|
||||||
|
* Pure, dependency-free (no xterm/DOM) so it's unit-testable in node.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derive the **current folder** from the terminal's OSC title.
|
||||||
|
*
|
||||||
|
* Most shells emit titles like `user@host:~/path/to/dir` (zsh/bash default).
|
||||||
|
* Take the part after the last ':' (the path), then its last segment (the
|
||||||
|
* folder name). `~` (home) stays `~`. Returns null for an empty title; falls
|
||||||
|
* back to the raw-ish basename when there's no `user@host:` shape (e.g. a
|
||||||
|
* running command title).
|
||||||
|
*/
|
||||||
|
export function folderFromTitle(title: string): string | null {
|
||||||
|
let s = title.trim()
|
||||||
|
if (!s) return null
|
||||||
|
const colon = s.lastIndexOf(':')
|
||||||
|
if (colon !== -1) s = s.slice(colon + 1).trim()
|
||||||
|
s = s.replace(/\/+$/, '') // strip trailing slash
|
||||||
|
const slash = s.lastIndexOf('/')
|
||||||
|
if (slash !== -1) s = s.slice(slash + 1)
|
||||||
|
return s || null
|
||||||
|
}
|
||||||
33
test/title-util.test.ts
Normal file
33
test/title-util.test.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import { folderFromTitle } from '../public/title-util.js'
|
||||||
|
|
||||||
|
describe('folderFromTitle', () => {
|
||||||
|
it('extracts the folder from user@host:~/path', () => {
|
||||||
|
expect(folderFromTitle('yiukai@Mac:~/Documents/git/web-terminal')).toBe('web-terminal')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('keeps ~ for the home directory', () => {
|
||||||
|
expect(folderFromTitle('yiukai@Yiukais-MacBook-Pro:~')).toBe('~')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('takes the basename of an absolute path', () => {
|
||||||
|
expect(folderFromTitle('user@host:/Users/yiukai/web')).toBe('web')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('strips a trailing slash', () => {
|
||||||
|
expect(folderFromTitle('user@host:~/web/')).toBe('web')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles a title with no user@host: prefix', () => {
|
||||||
|
expect(folderFromTitle('web-terminal')).toBe('web-terminal')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns the last path segment when the whole title is a path', () => {
|
||||||
|
expect(folderFromTitle('/var/log/nginx')).toBe('nginx')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null for an empty/whitespace title', () => {
|
||||||
|
expect(folderFromTitle(' ')).toBeNull()
|
||||||
|
expect(folderFromTitle('')).toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user