Skip to content

Commit

Permalink
fix(cursor): Change cursor to a class for better chaining
Browse files Browse the repository at this point in the history
  • Loading branch information
jamonholmgren committed Oct 15, 2023
1 parent 874d6e9 commit 7d4e0d3
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 80 deletions.
Binary file modified bun.lockb
Binary file not shown.
60 changes: 32 additions & 28 deletions src/_types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,34 +182,38 @@ export declare const cursorCodes: {
/**
* Moving the cursor around the terminal. Needs testing on Windows.
*/
export declare const cursor: {
write: (s: string) => any;
up: (count?: number) => any;
down: (count?: number) => any;
forward: (count?: number) => any;
back: (count?: number) => any;
moveDown: (count?: number) => any;
moveUp: (count?: number) => any;
backToStart: () => any;
horizontalAbsolute: (count?: number) => any;
eraseBefore: (count?: number) => any;
eraseLine: () => any;
erase: (count?: number) => any;
clearScreen: () => any;
scrollUp: (count?: number) => any;
scrollDown: (count?: number) => any;
goto: (pos: CursorPos) => any;
savePosition: () => any;
restorePosition: () => any;
hide: () => any;
show: () => any;
backspace: (count?: number) => any;
alternate: (enabled: boolean) => any;
queryPosition: typeof queryPosition;
bookmark: (name: string, pos?: CursorPos) => Promise<CursorPos>;
getBookmark: (name: string) => CursorPos;
jump: (name: string) => any;
};
export declare class Cursor {
bookmarks: {
[key: string]: CursorPos;
};
c(s: string, esc?: string): this;
write(s: string): this;
up(count?: number): this;
down(count?: number): this;
forward(count?: number): this;
back(count?: number): this;
moveDown(count?: number): this;
moveUp(count?: number): this;
backToStart(): this;
horizontalAbsolute(count?: number): this;
eraseBefore(count?: number): this;
eraseLine(): this;
erase(count?: number): this;
clearScreen(): this;
scrollUp(count?: number): this;
scrollDown(count?: number): this;
goto(pos: CursorPos): this;
savePosition(): this;
restorePosition(): this;
hide(): this;
show(): this;
backspace(count?: number): this;
alternate(enabled: boolean): this;
queryPosition(): Promise<CursorPos>;
bookmark(name: string, pos?: CursorPos): Promise<CursorPos>;
jump(name: string): this;
}
export declare const cursor: Cursor;
export declare function queryPosition(): Promise<CursorPos>;
/**
* Start a spinner. Returns a Timer object that can be used to stop the spinner.
Expand Down
144 changes: 92 additions & 52 deletions src/cursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,70 +34,110 @@ export const cursorCodes = {
show: "?25h",
} as const

/**
* For storing bookmarks
*/
const positions: { [key: string]: CursorPos } = {}

/**
* For chaining cursor methods.
*/
const c = (s: string, esc: string = ESC) => {
write(esc + s)
return cursor
}

/**
* Moving the cursor around the terminal. Needs testing on Windows.
*/
export const cursor = {
write: (s: string) => c(s, ""),
up: (count: number = 1) => c(`${count}${cursorCodes.up}`),
down: (count: number = 1) => c(`${count}${cursorCodes.down}`),
forward: (count: number = 1) => c(`${count}${cursorCodes.forward}`),
back: (count: number = 1) => c(`${count}${cursorCodes.back}`),
moveDown: (count: number = 1) => c(`${count}${cursorCodes.nextLine}`),
moveUp: (count: number = 1) => c(`${count}${cursorCodes.previousLine}`),
backToStart: () => c(`${cursorCodes.horizontalAbsolute}`),
horizontalAbsolute: (count = 1) => c(`${count}${cursorCodes.horizontalAbsolute}`),
eraseBefore: (count = 1) => c(`${count}${cursorCodes.eraseData}`),
eraseLine: () => c(`${cursorCodes.eraseLine}`),
erase: (count = 1) => c(`${count}${cursorCodes.eraseCharacter}`),
clearScreen: () => c(`${cursorCodes.clearScreen}`),
scrollUp: (count = 1) => c(`${count}${cursorCodes.scrollUp}`),
scrollDown: (count = 1) => c(`${count}${cursorCodes.scrollDown}`),
goto: (pos: CursorPos) => c(cursorCodes.goToPosition(pos.cols, pos.rows), ""),
export class Cursor {
bookmarks: { [key: string]: CursorPos } = {}

// for chaining easily
c(s: string, esc: string = ESC) {
write(esc + s)
return this
}

write(s: string) {
return this.c(s, "")
}
up(count: number = 1) {
return this.c(`${count}${cursorCodes.up}`)
}
down(count: number = 1) {
return this.c(`${count}${cursorCodes.down}`)
}
forward(count: number = 1) {
return this.c(`${count}${cursorCodes.forward}`)
}
back(count: number = 1) {
return this.c(`${count}${cursorCodes.back}`)
}
moveDown(count: number = 1) {
return this.c(`${count}${cursorCodes.nextLine}`)
}
moveUp(count: number = 1) {
return this.c(`${count}${cursorCodes.previousLine}`)
}
backToStart() {
return this.c(`${cursorCodes.horizontalAbsolute}`)
}
horizontalAbsolute(count = 1) {
return this.c(`${count}${cursorCodes.horizontalAbsolute}`)
}
eraseBefore(count = 1) {
return this.c(`${count}${cursorCodes.eraseData}`)
}
eraseLine() {
return this.c(`${cursorCodes.eraseLine}`)
}
erase(count = 1) {
return this.c(`${count}${cursorCodes.eraseCharacter}`)
}
clearScreen() {
return this.c(`${cursorCodes.clearScreen}`)
}
scrollUp(count = 1) {
return this.c(`${count}${cursorCodes.scrollUp}`)
}
scrollDown(count = 1) {
return this.c(`${count}${cursorCodes.scrollDown}`)
}
goto(pos: CursorPos) {
return this.c(cursorCodes.goToPosition(pos.cols, pos.rows), "")
}

// basic save & restore position
savePosition: () => c(`${cursorCodes.savePosition}`, ""),
restorePosition: () => c(`${cursorCodes.restorePosition}`, ""),

hide: () => c(`${cursorCodes.hide}`),
show: () => c(`${cursorCodes.show}`),

backspace: (count = 1) => cursor.back(count).erase(count),

alternate: (enabled: boolean) =>
c(`${enabled ? cursorCodes.enterAlternativeScreen : cursorCodes.exitAlternativeScreen}`),

// advanced save & restore positions -- these can't be chained
queryPosition,
bookmark: async (name: string, pos?: CursorPos) => {
savePosition() {
return this.c(`${cursorCodes.savePosition}`, "")
}
restorePosition() {
return this.c(`${cursorCodes.restorePosition}`, "")
}

hide() {
return this.c(`${cursorCodes.hide}`)
}
show() {
return this.c(`${cursorCodes.show}`)
}

backspace(count = 1) {
return this.back(count).erase(count)
}

alternate(enabled: boolean) {
return this.c(`${enabled ? cursorCodes.enterAlternativeScreen : cursorCodes.exitAlternativeScreen}`)
}

// advanced save & restore bookmarks -- these can't be chained
queryPosition() {
return queryPosition()
}
async bookmark(name: string, pos?: CursorPos) {
const cpos = pos || (await queryPosition())
positions[name] = cpos
this.bookmarks[name] = cpos
return cpos
},
getBookmark: (name: string) => positions[name],
}

// can be chained, since we don't have to wait for the queryPosition
jump: (name: string) => {
const pos = positions[name]
jump(name: string) {
const pos = this.bookmarks[name]
if (!pos) throw new Error(`No cursor bookmark found with name ${name}`)
cursor.goto(pos)
return cursor
},
return this.goto(pos)
}
}

export const cursor = new Cursor()

// this is how we use the ansi queryPosition escape code.
// it returns the cursor position, which we can then parse
// and use to position the cursor.
Expand Down

0 comments on commit 7d4e0d3

Please # to comment.