@readwise/cli 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/tui/term.ts CHANGED
@@ -22,12 +22,16 @@ export const style = {
22
22
 
23
23
  export function enterFullScreen(): void {
24
24
  process.stdout.write(`${ESC}[?1049h`); // alternate screen buffer
25
+ process.stdout.write(`${ESC}[?2004h`); // enable bracketed paste mode
26
+ process.stdout.write(`${ESC}[>1u`); // enable kitty keyboard protocol (disambiguate mode)
25
27
  process.stdout.write(`${ESC}[?25l`); // hide cursor
26
28
  process.stdout.write(`${ESC}[H`); // cursor home
27
29
  }
28
30
 
29
31
  export function exitFullScreen(): void {
30
32
  process.stdout.write(`${ESC}[?25h`); // show cursor
33
+ process.stdout.write(`${ESC}[<u`); // disable kitty keyboard protocol
34
+ process.stdout.write(`${ESC}[?2004l`); // disable bracketed paste mode
31
35
  process.stdout.write(`${ESC}[?1049l`); // restore screen buffer
32
36
  }
33
37
 
@@ -37,11 +41,12 @@ export function paint(lines: string[]): void {
37
41
  let out = `${ESC}[H`; // cursor home
38
42
  const count = Math.min(lines.length, rows);
39
43
  for (let i = 0; i < count; i++) {
40
- out += lines[i] + `${ESC}[K\n`; // line content + clear to end of line
44
+ out += lines[i] + `${ESC}[K`; // line content + clear to end of line
45
+ if (i < count - 1) out += "\n";
41
46
  }
42
47
  // Clear any remaining lines below content
43
48
  if (count < rows) {
44
- out += `${ESC}[J`; // clear from cursor to end of screen
49
+ out += `\n${ESC}[J`; // clear from cursor to end of screen
45
50
  }
46
51
  process.stdout.write(out);
47
52
  }
@@ -61,6 +66,25 @@ export interface KeyEvent {
61
66
 
62
67
  export function parseKey(data: Buffer): KeyEvent {
63
68
  const s = data.toString("utf-8");
69
+
70
+ // Bracketed paste: \x1b[200~ ... \x1b[201~
71
+ if (s.startsWith(`${ESC}[200~`)) {
72
+ const content = s.replace(/\x1b\[200~/g, "").replace(/\x1b\[201~/g, "");
73
+ const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
74
+ return { raw: normalized, name: "paste", shift: false, ctrl: false };
75
+ }
76
+
77
+ // Alt+Enter (ESC + CR/LF) — reliable newline insertion across all terminals
78
+ if (s === `${ESC}\r` || s === `${ESC}\n`) {
79
+ return { raw: s, name: "return", shift: true, ctrl: false };
80
+ }
81
+
82
+ // Alt+Arrow (word navigation)
83
+ if (s === `${ESC}[1;3D` || s === `${ESC}b`) return { raw: s, name: "wordLeft", shift: false, ctrl: false };
84
+ if (s === `${ESC}[1;3C` || s === `${ESC}f`) return { raw: s, name: "wordRight", shift: false, ctrl: false };
85
+ // Alt+Backspace (word delete)
86
+ if (s === `${ESC}\x7f`) return { raw: s, name: "wordBackspace", shift: false, ctrl: false };
87
+
64
88
  const ctrl = s.length === 1 && s.charCodeAt(0) < 32;
65
89
 
66
90
  // Escape sequences
@@ -73,10 +97,15 @@ export function parseKey(data: Buffer): KeyEvent {
73
97
  if (s === `${ESC}[Z`) return { raw: s, name: "tab", shift: true, ctrl: false };
74
98
  if (s === ESC || s === `${ESC}${ESC}`) return { raw: s, name: "escape", shift: false, ctrl: false };
75
99
 
76
- // Shift+Enter sequences
77
- if (s === `${ESC}[13;2u`) return { raw: s, name: "return", shift: true, ctrl: false }; // CSI u / kitty
78
- if (s === `${ESC}[27;2;13~`) return { raw: s, name: "return", shift: true, ctrl: false }; // xterm
79
- if (s === `${ESC}OM`) return { raw: s, name: "return", shift: true, ctrl: false }; // misc terminals
100
+ // Kitty keyboard protocol CSI-u encodings (when disambiguate mode is active)
101
+ if (s === `${ESC}[13;2u`) return { raw: s, name: "return", shift: true, ctrl: false }; // Shift+Enter
102
+ if (s === `${ESC}[27;2;13~`) return { raw: s, name: "return", shift: true, ctrl: false }; // Shift+Enter (xterm)
103
+ if (s === `${ESC}OM`) return { raw: s, name: "return", shift: true, ctrl: false }; // Shift+Enter (misc)
104
+ if (s === `${ESC}[27u`) return { raw: s, name: "escape", shift: false, ctrl: false };
105
+ if (s === `${ESC}[13u`) return { raw: s, name: "return", shift: false, ctrl: false };
106
+ if (s === `${ESC}[9u`) return { raw: s, name: "tab", shift: false, ctrl: false };
107
+ if (s === `${ESC}[9;2u`) return { raw: s, name: "tab", shift: true, ctrl: false };
108
+ if (s === `${ESC}[127u`) return { raw: s, name: "backspace", shift: false, ctrl: false };
80
109
 
81
110
  // Single characters
82
111
  if (s === "\r" || s === "\n") return { raw: s, name: "return", shift: false, ctrl: false };
@@ -89,6 +118,12 @@ export function parseKey(data: Buffer): KeyEvent {
89
118
  return { raw: s, name: String.fromCharCode(s.charCodeAt(0) + 96), shift: false, ctrl: true };
90
119
  }
91
120
 
121
+ // Multi-character non-escape input = paste (terminal without bracketed paste support)
122
+ if (s.length > 1 && !s.startsWith(ESC)) {
123
+ const normalized = s.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
124
+ return { raw: normalized, name: "paste", shift: false, ctrl: false };
125
+ }
126
+
92
127
  return { raw: s, name: s, shift: false, ctrl: false };
93
128
  }
94
129