leet-tui 0.1.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/README.md ADDED
@@ -0,0 +1,149 @@
1
+ # LeetCode TUI
2
+
3
+ A terminal user interface (TUI) application for solving LeetCode problems with an embedded Neovim editor.
4
+
5
+ ## Features
6
+
7
+ - **Home page with problem list**: Browse all available LeetCode problems with difficulty indicators
8
+ - **Split-screen interface**: 1/3 for problem description, 2/3 for Neovim editor
9
+ - **Automatic boilerplate generation**: Each problem gets custom boilerplate code and test cases
10
+ - **Run tests locally**: Press Ctrl+R to run your solution against test cases with instant feedback
11
+ - **Color-coded results**: Test output shows PASSED in green, FAILED in red
12
+ - **Embedded Neovim**: Full Neovim functionality within the TUI
13
+ - **JavaScript solutions**: Default language is JavaScript (other languages coming soon)
14
+ - **Organized solution files**: Solutions saved to `~/.local/share/leet-tui/solutions/`
15
+ - **Proper window sizing**: Neovim automatically fits the available space
16
+ - **Focus switching**: Toggle between question pane and editor pane
17
+ - **Scrollable question view**: Navigate through problem descriptions
18
+ - **Proper input forwarding**: All keyboard shortcuts work in Neovim (arrows, Ctrl combinations, etc.)
19
+
20
+ ## Architecture
21
+
22
+ The application uses:
23
+ - **ratatui**: Terminal UI framework
24
+ - **portable-pty**: PTY system for spawning Neovim
25
+ - **vt100**: Terminal emulator parser
26
+ - **tui-term**: Widget for rendering terminal output
27
+ - **crossterm**: Cross-platform terminal manipulation
28
+
29
+ ## Installation
30
+
31
+ ### Via npm (recommended)
32
+
33
+ ```bash
34
+ npm install -g leet-tui
35
+ ```
36
+
37
+ This will automatically download the correct binary for your platform.
38
+
39
+ ### From source
40
+
41
+ Prerequisites:
42
+ - Rust (1.70+)
43
+ - Neovim installed and available in PATH
44
+
45
+ ```bash
46
+ cargo build --release
47
+ ./target/release/leet-tui
48
+ ```
49
+
50
+ ## Running
51
+
52
+ ```bash
53
+ leet-tui
54
+ ```
55
+
56
+ ## Keyboard Shortcuts
57
+
58
+ ### Home Page
59
+ - **Up/Down**: Navigate problem list
60
+ - **Enter**: Select a problem
61
+ - **Ctrl+C**: Quit application
62
+
63
+ ### Question View
64
+ - **Ctrl+R**: Save file and run tests (auto-saves before testing)
65
+ - **Ctrl+Q**: Switch focus between question pane and editor pane
66
+ - **Ctrl+H**: Back to home page
67
+ - **Ctrl+C**: Quit application
68
+ - **Esc**: Close test results popup
69
+ - **Up/Down**: Scroll question (when question pane is focused)
70
+ - **PageUp/PageDown**: Fast scroll question (when question pane is focused)
71
+ - **Home**: Jump to top of question (when question pane is focused)
72
+ - **All Neovim shortcuts**: Work when editor pane is focused
73
+
74
+ ## Project Structure
75
+
76
+ ```
77
+ src/
78
+ ├── main.rs # Main application logic and UI rendering
79
+ ├── pty.rs # PTY manager for Neovim integration
80
+ ├── input.rs # Keyboard input handling and ANSI escape sequence mapping
81
+ └── leetcode.rs # LeetCode API integration (currently with sample data)
82
+ ```
83
+
84
+ ## How It Works
85
+
86
+ ### PTY Integration
87
+
88
+ The application spawns Neovim in a pseudo-terminal (PTY) and renders its output in a Ratatui widget. A background thread continuously reads from the PTY and updates a VT100 parser, which maintains the terminal state.
89
+
90
+ ### Input Forwarding
91
+
92
+ When the editor pane is focused, all keyboard events are converted to ANSI escape sequences and sent to the PTY. This includes:
93
+ - Regular characters
94
+ - Control combinations (Ctrl+A, Ctrl+W, etc.)
95
+ - Arrow keys and function keys
96
+ - Special keys (Home, End, PageUp, PageDown, etc.)
97
+
98
+ ### Focus Management
99
+
100
+ The application maintains a focus state that determines which pane receives input:
101
+ - **Question pane**: Arrow keys scroll the problem description
102
+ - **Editor pane**: All input is forwarded to Neovim
103
+
104
+ ## Extending
105
+
106
+ ### Adding Real LeetCode API Integration
107
+
108
+ Currently, the app displays a sample problem. To integrate with the real LeetCode API:
109
+
110
+ 1. Update `src/leetcode.rs` to make GraphQL requests to LeetCode
111
+ 2. Add authentication support
112
+ 3. Implement problem selection UI
113
+
114
+ ### Customizing Neovim
115
+
116
+ You can customize the Neovim instance by:
117
+ - Setting environment variables (e.g., `NVIM_APPNAME` for different configs)
118
+ - Passing command-line arguments in `src/pty.rs:25`
119
+ - Creating a custom init file for the TUI context
120
+
121
+ The app currently opens `solution.js` by default. To support other languages, modify the file extension in `src/pty.rs:25`.
122
+
123
+ ## Troubleshooting
124
+
125
+ ### Keys not working in Neovim
126
+
127
+ If certain key combinations don't work, you may need to add them to the `key_to_bytes` function in `src/input.rs`.
128
+
129
+ ### Terminal size issues
130
+
131
+ The application handles resize events and updates the PTY size accordingly. If you experience rendering issues, try resizing your terminal.
132
+
133
+ ### Neovim colors look wrong
134
+
135
+ The VT100 parser supports basic colors. If you use a complex Neovim theme, some colors may not render perfectly in the embedded terminal.
136
+
137
+ ## Future Enhancements
138
+
139
+ - [ ] Real LeetCode API integration
140
+ - [ ] Problem selection menu
141
+ - [ ] Multiple test case support
142
+ - [ ] Submit solution functionality
143
+ - [ ] History of attempted problems
144
+ - [ ] Custom Neovim configuration per problem type
145
+ - [ ] Split terminal for running tests
146
+
147
+ ## License
148
+
149
+ MIT
package/bin/leet-tui ADDED
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execFileSync, spawn } = require('child_process');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+
7
+ const binDir = __dirname;
8
+ const binName = process.platform === 'win32' ? 'leet-tui.exe' : 'leet-tui-binary';
9
+ const binaryPath = path.join(binDir, binName);
10
+
11
+ if (!fs.existsSync(binaryPath)) {
12
+ console.error('[leet-tui] Binary not found. Running postinstall...');
13
+ try {
14
+ execFileSync('node', [path.join(__dirname, '..', 'scripts', 'postinstall.js')], {
15
+ stdio: 'inherit',
16
+ });
17
+ } catch (err) {
18
+ console.error('[leet-tui] Failed to install binary.');
19
+ process.exit(1);
20
+ }
21
+ }
22
+
23
+ if (!fs.existsSync(binaryPath)) {
24
+ console.error('[leet-tui] Binary still not found after postinstall.');
25
+ console.error('Please try reinstalling: npm install -g leet-tui');
26
+ process.exit(1);
27
+ }
28
+
29
+ // Use spawn with inherit to properly handle TTY
30
+ const child = spawn(binaryPath, process.argv.slice(2), {
31
+ stdio: 'inherit',
32
+ env: process.env,
33
+ });
34
+
35
+ child.on('error', (err) => {
36
+ console.error(`[leet-tui] Failed to start: ${err.message}`);
37
+ process.exit(1);
38
+ });
39
+
40
+ child.on('exit', (code, signal) => {
41
+ if (signal) {
42
+ process.exit(1);
43
+ }
44
+ process.exit(code ?? 0);
45
+ });
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "leet-tui",
3
+ "version": "0.1.0",
4
+ "description": "A terminal UI for practicing LeetCode Blind 75 problems with embedded Neovim",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/trevor-ofarrell/leet-tui"
9
+ },
10
+ "homepage": "https://github.com/trevor-ofarrell/leet-tui#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/trevor-ofarrell/leet-tui/issues"
13
+ },
14
+ "keywords": [
15
+ "leetcode",
16
+ "tui",
17
+ "terminal",
18
+ "neovim",
19
+ "coding",
20
+ "interview",
21
+ "practice",
22
+ "blind75"
23
+ ],
24
+ "bin": {
25
+ "leet-tui": "bin/leet-tui"
26
+ },
27
+ "files": [
28
+ "bin",
29
+ "scripts"
30
+ ],
31
+ "scripts": {
32
+ "postinstall": "node scripts/postinstall.js"
33
+ },
34
+ "engines": {
35
+ "node": ">=16"
36
+ }
37
+ }
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const https = require('https');
6
+ const { execSync } = require('child_process');
7
+
8
+ const REPO = 'trevor-ofarrell/leet-tui';
9
+ const VERSION = require('../package.json').version;
10
+
11
+ const PLATFORMS = {
12
+ 'darwin-arm64': 'leet-tui-darwin-arm64',
13
+ 'darwin-x64': 'leet-tui-darwin-x64',
14
+ 'linux-x64': 'leet-tui-linux-x64',
15
+ 'linux-arm64': 'leet-tui-linux-arm64',
16
+ 'win32-x64': 'leet-tui-win32-x64.exe',
17
+ };
18
+
19
+ const platformKey = `${process.platform}-${process.arch}`;
20
+ const binaryName = PLATFORMS[platformKey];
21
+
22
+ if (!binaryName) {
23
+ console.warn(`\n[leet-tui] No prebuilt binary for ${platformKey}`);
24
+ console.warn(`Supported: ${Object.keys(PLATFORMS).join(', ')}`);
25
+ console.warn('Build from source: https://github.com/' + REPO + '\n');
26
+ process.exit(0);
27
+ }
28
+
29
+ const binDir = path.join(__dirname, '..', 'bin');
30
+ const binPath = path.join(binDir, process.platform === 'win32' ? 'leet-tui.exe' : 'leet-tui-binary');
31
+ const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${binaryName}`;
32
+
33
+ function download(url, dest) {
34
+ return new Promise((resolve, reject) => {
35
+ const file = fs.createWriteStream(dest);
36
+
37
+ https.get(url, (response) => {
38
+ // Handle redirects (GitHub releases redirect to S3)
39
+ if (response.statusCode === 302 || response.statusCode === 301) {
40
+ file.close();
41
+ fs.unlinkSync(dest);
42
+ return download(response.headers.location, dest).then(resolve).catch(reject);
43
+ }
44
+
45
+ if (response.statusCode !== 200) {
46
+ file.close();
47
+ fs.unlinkSync(dest);
48
+ reject(new Error(`Download failed: HTTP ${response.statusCode}`));
49
+ return;
50
+ }
51
+
52
+ const total = parseInt(response.headers['content-length'], 10);
53
+ let downloaded = 0;
54
+
55
+ response.on('data', (chunk) => {
56
+ downloaded += chunk.length;
57
+ if (total) {
58
+ const pct = Math.round((downloaded / total) * 100);
59
+ process.stdout.write(`\r[leet-tui] Downloading... ${pct}%`);
60
+ }
61
+ });
62
+
63
+ response.pipe(file);
64
+
65
+ file.on('finish', () => {
66
+ file.close();
67
+ console.log('\r[leet-tui] Download complete. ');
68
+ resolve();
69
+ });
70
+ }).on('error', (err) => {
71
+ fs.unlink(dest, () => {});
72
+ reject(err);
73
+ });
74
+ });
75
+ }
76
+
77
+ async function main() {
78
+ console.log(`[leet-tui] Installing binary for ${platformKey}...`);
79
+
80
+ // Ensure bin directory exists
81
+ if (!fs.existsSync(binDir)) {
82
+ fs.mkdirSync(binDir, { recursive: true });
83
+ }
84
+
85
+ try {
86
+ await download(url, binPath);
87
+
88
+ // Make executable on Unix
89
+ if (process.platform !== 'win32') {
90
+ fs.chmodSync(binPath, 0o755);
91
+ }
92
+
93
+ console.log('[leet-tui] Installation successful!');
94
+ } catch (err) {
95
+ console.error(`\n[leet-tui] Failed to download binary: ${err.message}`);
96
+ console.error(`URL: ${url}`);
97
+ console.error('\nYou can build from source:');
98
+ console.error(' git clone https://github.com/' + REPO);
99
+ console.error(' cd leet-tui && cargo build --release');
100
+ process.exit(1);
101
+ }
102
+ }
103
+
104
+ main();