rufio 0.70.0 → 0.80.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -0
- data/README.md +29 -10
- data/README_ja.md +30 -10
- data/docs/CHANGELOG_v0.80.0.md +189 -0
- data/lib/rufio/ansi_line_parser.rb +103 -0
- data/lib/rufio/command_mode.rb +61 -16
- data/lib/rufio/config.rb +2 -0
- data/lib/rufio/file_preview.rb +26 -1
- data/lib/rufio/health_checker.rb +22 -0
- data/lib/rufio/keybind_handler.rb +8 -3
- data/lib/rufio/renderer.rb +11 -4
- data/lib/rufio/screen.rb +3 -2
- data/lib/rufio/script_runner.rb +21 -4
- data/lib/rufio/selection_manager.rb +6 -0
- data/lib/rufio/syntax_highlighter.rb +111 -0
- data/lib/rufio/terminal_ui.rb +135 -30
- data/lib/rufio/version.rb +1 -1
- data/lib/rufio.rb +2 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a5c4fc956ed9bcb90569c8c7fcdd023fec3d31cdf1852f075ba40c9746a654dc
|
|
4
|
+
data.tar.gz: f07ae9f1f0071de307b407e5322f3f2a53c62fb654f023223f7214be79350dc3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '097aed068bd564b961c159504b7396c4d0dec7b9eb4be819d2f83380fc10aa86e698c316b9e95bfb012863790ea53cbe0ee521d3561919cdde61fbafcbb22959'
|
|
7
|
+
data.tar.gz: 6d1d2ce8640debc3fe6fbe282e75a959bb4c7a846f21cde65d0a4cd2a713329c7416591c2d182b05e7bd67b4df9895071cac32dc9ab85940459eed8f19660881
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.80.0] - 2026-02-21
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Syntax Highlighting**: File preview now supports syntax highlighting via `bat`
|
|
14
|
+
- 19 languages supported: Ruby, Python, JS, TS, Go, Rust, Shell, C, C++, Java, SQL, TOML, YAML, JSON, HTML, CSS, Markdown, Dockerfile, Makefile
|
|
15
|
+
- Graceful fallback to plain text when `bat` is not installed
|
|
16
|
+
- mtime-based cache for instant re-display of previously viewed files
|
|
17
|
+
- Health check support (`rufio -c`) now reports `bat` availability
|
|
18
|
+
- **New classes**: `AnsiLineParser`, `SyntaxHighlighter`
|
|
19
|
+
- **New tests**: `test_ansi_line_parser.rb` (25 tests), `test_syntax_highlighter.rb` (+7 async tests)
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- **Cursor flickering** when navigating source directories: Changed `Renderer#render` from per-line `print` (immediate flush) to a single buffered `write` call — terminal updates are now atomic
|
|
23
|
+
- **Navigation lag** when moving between source files: `bat` is now executed asynchronously in a background thread; the current frame immediately falls back to plain text, then re-renders with highlighting on completion
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- **`Renderer#render`**: Replaced row-by-row `print` with single `write(buf)` + `flush` for atomic output
|
|
27
|
+
- **`SyntaxHighlighter`**: Added `highlight_async` (Thread + Mutex + pending guard) alongside existing `highlight`
|
|
28
|
+
- **`TerminalUI`**: Preview caching extended with `highlighted` / `highlighted_wrapped` keys; `@highlight_updated` flag added for async re-render notification
|
|
29
|
+
- **`FilePreview#determine_file_type`**: Added Go, Rust, Shell, TOML, SQL, C, C++, Java, Dockerfile, Makefile language detection
|
|
30
|
+
|
|
31
|
+
### Technical Details
|
|
32
|
+
- **New files**: `lib/rufio/ansi_line_parser.rb`, `lib/rufio/syntax_highlighter.rb`
|
|
33
|
+
- **Modified files**: `lib/rufio/renderer.rb`, `lib/rufio/terminal_ui.rb`, `lib/rufio/file_preview.rb`, `lib/rufio/health_checker.rb`, `lib/rufio/config.rb`, `lib/rufio.rb`
|
|
34
|
+
- **For details**: [CHANGELOG_v0.80.0.md](./docs/CHANGELOG_v0.80.0.md)
|
|
35
|
+
|
|
36
|
+
## [0.71.0] - 2026-02-16
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
- **Script Arguments**: Pass arguments to scripts via command mode (e.g., `@retag.sh v0.70.0`)
|
|
40
|
+
- Supports both ScriptRunner and LocalScriptScanner
|
|
41
|
+
- **Script/Rake Execution Logging**: Log execution results of `@script` and `rake:task` commands to CommandLogger
|
|
42
|
+
- Covers both synchronous and background execution
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
- **Selection across directories**: Clear selection state when navigating to a different directory
|
|
46
|
+
- **Help/Log viewer entries**: Exclude `..` entry in help mode and log viewer mode
|
|
47
|
+
|
|
10
48
|
## [0.70.0] - 2026-02-14
|
|
11
49
|
|
|
12
50
|
### Added
|
|
@@ -599,6 +637,7 @@ For detailed information, see [CHANGELOG_v0.4.0.md](./docs/CHANGELOG_v0.4.0.md)
|
|
|
599
637
|
|
|
600
638
|
### Detailed Release Notes
|
|
601
639
|
|
|
640
|
+
- [v0.80.0](./docs/CHANGELOG_v0.80.0.md) - Syntax Highlighting & Rendering Fixes
|
|
602
641
|
- [v0.31.0](./docs/CHANGELOG_v0.31.0.md) - Experimental Native Scanner Implementation
|
|
603
642
|
- [v0.30.0](./docs/CHANGELOG_v0.30.0.md) - Help System Overhaul
|
|
604
643
|
- [v0.21.0](./docs/CHANGELOG_v0.21.0.md) - Copy Feature & Code Refactoring
|
data/README.md
CHANGED
|
@@ -40,7 +40,7 @@ rufio is not just a file manager. It's a **tool runtime execution environment**.
|
|
|
40
40
|
### As a File Manager
|
|
41
41
|
|
|
42
42
|
- **Vim-like Key Bindings**: Intuitive navigation
|
|
43
|
-
- **Real-time Preview**: Instantly display file contents
|
|
43
|
+
- **Real-time Preview**: Instantly display file contents with syntax highlighting (via `bat`)
|
|
44
44
|
- **Fast Search**: Integration with fzf/rga
|
|
45
45
|
- **Bookmarks**: Quick access to frequently used directories
|
|
46
46
|
- **zoxide Integration**: Smart directory history
|
|
@@ -290,22 +290,41 @@ script_paths:
|
|
|
290
290
|
|
|
291
291
|
## External Tool Integration
|
|
292
292
|
|
|
293
|
-
rufio integrates with the following external tools to extend functionality
|
|
293
|
+
rufio integrates with the following external tools to extend functionality.
|
|
294
|
+
All tools are **optional** — rufio works without them, falling back to built-in behavior.
|
|
294
295
|
|
|
295
|
-
| Tool | Purpose | Key |
|
|
296
|
-
|
|
297
|
-
| fzf | File name search | `s` |
|
|
298
|
-
| rga | File content search | `F` |
|
|
299
|
-
| zoxide | Directory history | `z` |
|
|
296
|
+
| Tool | Purpose | Key | Required? |
|
|
297
|
+
|------|---------|-----|-----------|
|
|
298
|
+
| fzf | File name search | `s` | Optional |
|
|
299
|
+
| rga | File content search | `F` | Optional |
|
|
300
|
+
| zoxide | Directory history | `z` | Optional |
|
|
301
|
+
| bat | Syntax highlighting in preview | — | Optional |
|
|
300
302
|
|
|
301
|
-
###
|
|
303
|
+
### bat — Syntax Highlighting
|
|
304
|
+
|
|
305
|
+
When `bat` is installed, code files in the preview pane are displayed with full syntax
|
|
306
|
+
highlighting (Ruby, Python, Go, Rust, TypeScript, and 15+ more languages).
|
|
307
|
+
|
|
308
|
+
Highlighting is loaded asynchronously — navigation stays fast even on large source trees.
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
# macOS
|
|
312
|
+
brew install bat
|
|
313
|
+
|
|
314
|
+
# Ubuntu/Debian
|
|
315
|
+
apt install bat
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Run `rufio -c` to verify bat is detected correctly.
|
|
319
|
+
|
|
320
|
+
### Installation (all tools)
|
|
302
321
|
|
|
303
322
|
```bash
|
|
304
323
|
# macOS
|
|
305
|
-
brew install fzf rga zoxide
|
|
324
|
+
brew install fzf bat rga zoxide
|
|
306
325
|
|
|
307
326
|
# Ubuntu/Debian
|
|
308
|
-
apt install fzf zoxide
|
|
327
|
+
apt install fzf bat zoxide
|
|
309
328
|
# rga requires separate installation: https://github.com/phiresky/ripgrep-all
|
|
310
329
|
```
|
|
311
330
|
|
data/README_ja.md
CHANGED
|
@@ -42,7 +42,7 @@ rufioは単なるファイルマネージャーではありません。**ツー
|
|
|
42
42
|
### ファイルマネージャーとして
|
|
43
43
|
|
|
44
44
|
- **Vimライクなキーバインド**: 直感的なナビゲーション
|
|
45
|
-
- **リアルタイムプレビュー**:
|
|
45
|
+
- **リアルタイムプレビュー**: ファイル内容を即座に表示(`bat` によるシンタックスハイライト対応)
|
|
46
46
|
- **高速検索**: fzf/rgaとの連携
|
|
47
47
|
- **ブックマーク**: よく使うディレクトリに素早くアクセス
|
|
48
48
|
- **zoxide連携**: スマートなディレクトリ履歴
|
|
@@ -255,22 +255,42 @@ SCRIPT_PATHS = [
|
|
|
255
255
|
|
|
256
256
|
## 外部ツール連携
|
|
257
257
|
|
|
258
|
-
rufio
|
|
258
|
+
rufioは以下の外部ツールと連携して機能を拡張します。
|
|
259
|
+
すべて**オプション**です。インストールなしでも rufio は正常動作します。
|
|
259
260
|
|
|
260
|
-
| ツール | 用途 | キー |
|
|
261
|
-
|
|
262
|
-
| fzf | ファイル名検索 | `s` |
|
|
263
|
-
| rga | ファイル内容検索 | `F` |
|
|
264
|
-
| zoxide | ディレクトリ履歴 | `z` |
|
|
261
|
+
| ツール | 用途 | キー | 必須 |
|
|
262
|
+
|--------|------|------|------|
|
|
263
|
+
| fzf | ファイル名検索 | `s` | オプション |
|
|
264
|
+
| rga | ファイル内容検索 | `F` | オプション |
|
|
265
|
+
| zoxide | ディレクトリ履歴 | `z` | オプション |
|
|
266
|
+
| bat | プレビューのシンタックスハイライト | — | オプション |
|
|
265
267
|
|
|
266
|
-
###
|
|
268
|
+
### bat — シンタックスハイライト
|
|
269
|
+
|
|
270
|
+
`bat` をインストールすると、プレビューペインでコードファイルを開いた際に
|
|
271
|
+
シンタックスハイライトが表示されます(Ruby、Python、Go、Rust、TypeScript など15言語以上対応)。
|
|
272
|
+
|
|
273
|
+
ハイライトはバックグラウンドで非同期に読み込まれるため、大きなソースツリーでも
|
|
274
|
+
カーソル移動が重くなりません。
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
# macOS
|
|
278
|
+
brew install bat
|
|
279
|
+
|
|
280
|
+
# Ubuntu/Debian
|
|
281
|
+
apt install bat
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`rufio -c` を実行すると bat が正しく認識されているか確認できます。
|
|
285
|
+
|
|
286
|
+
### インストール(全ツール)
|
|
267
287
|
|
|
268
288
|
```bash
|
|
269
289
|
# macOS
|
|
270
|
-
brew install fzf rga zoxide
|
|
290
|
+
brew install fzf bat rga zoxide
|
|
271
291
|
|
|
272
292
|
# Ubuntu/Debian
|
|
273
|
-
apt install fzf zoxide
|
|
293
|
+
apt install fzf bat zoxide
|
|
274
294
|
# rgaは別途インストール: https://github.com/phiresky/ripgrep-all
|
|
275
295
|
```
|
|
276
296
|
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# CHANGELOG v0.80.0
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Adds syntax highlighting to the file preview pane. When `bat` is installed, code files
|
|
6
|
+
are displayed with full ANSI color highlighting. Environments without `bat` fall back
|
|
7
|
+
silently to plain text display.
|
|
8
|
+
|
|
9
|
+
Also fixes **cursor flickering** and **navigation lag** that occurred when browsing
|
|
10
|
+
source code directories with highlighting active.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## New Features
|
|
15
|
+
|
|
16
|
+
### Syntax Highlighting (via `bat`)
|
|
17
|
+
|
|
18
|
+
The preview pane now renders code files with syntax highlighting using `bat`'s ANSI
|
|
19
|
+
color output.
|
|
20
|
+
|
|
21
|
+
**Supported languages:**
|
|
22
|
+
|
|
23
|
+
| Language | Extensions |
|
|
24
|
+
|----------|------------|
|
|
25
|
+
| Ruby | `.rb` |
|
|
26
|
+
| Python | `.py` |
|
|
27
|
+
| JavaScript | `.js`, `.mjs` |
|
|
28
|
+
| TypeScript | `.ts` |
|
|
29
|
+
| HTML | `.html`, `.htm` |
|
|
30
|
+
| CSS | `.css` |
|
|
31
|
+
| JSON | `.json` |
|
|
32
|
+
| YAML | `.yml`, `.yaml` |
|
|
33
|
+
| Markdown | `.md`, `.markdown` |
|
|
34
|
+
| Go | `.go` |
|
|
35
|
+
| Rust | `.rs` |
|
|
36
|
+
| Shell | `.sh`, `.bash`, `.zsh` |
|
|
37
|
+
| TOML | `.toml` |
|
|
38
|
+
| SQL | `.sql` |
|
|
39
|
+
| C | `.c`, `.h` |
|
|
40
|
+
| C++ | `.cpp`, `.cc`, `.cxx`, `.hpp` |
|
|
41
|
+
| Java | `.java` |
|
|
42
|
+
| Dockerfile | `Dockerfile`, `Dockerfile.*` |
|
|
43
|
+
| Makefile | `Makefile`, `GNUmakefile` |
|
|
44
|
+
|
|
45
|
+
**Behavior:**
|
|
46
|
+
- Graceful degradation — plain text display when `bat` is not installed
|
|
47
|
+
- Non-UTF-8 files (e.g. Shift_JIS) skip highlighting automatically
|
|
48
|
+
- mtime-based cache: second visit to the same file is instant (0 ms)
|
|
49
|
+
- Run `rufio -c` to verify `bat` is detected by the health checker
|
|
50
|
+
|
|
51
|
+
**Installation:**
|
|
52
|
+
```bash
|
|
53
|
+
# macOS
|
|
54
|
+
brew install bat
|
|
55
|
+
|
|
56
|
+
# Ubuntu/Debian
|
|
57
|
+
apt install bat
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Bug Fixes
|
|
63
|
+
|
|
64
|
+
### Fix 1: Cursor Flickering (Renderer Atomic Output)
|
|
65
|
+
|
|
66
|
+
**Symptom:** The preview pane flickered briefly when moving the cursor in source
|
|
67
|
+
code directories.
|
|
68
|
+
|
|
69
|
+
**Root cause:** `Renderer#render` called `print` once per dirty row. With
|
|
70
|
+
`STDOUT sync=true`, each `print` flushes immediately, so intermediate states
|
|
71
|
+
(highlighted color removed, new color not yet drawn) were visible in the terminal.
|
|
72
|
+
|
|
73
|
+
**Fix:** All dirty row output is now accumulated into a single string buffer, then
|
|
74
|
+
written with one `write` + `flush` call — guaranteeing an atomic terminal update.
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
Before: print row0 → flush → print row1 → flush → ... (intermediate states visible)
|
|
78
|
+
After: buf = row0 + row1 + ... → write(buf) + flush (single atomic update)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Fix 2: Navigation Lag (Async bat Execution)
|
|
82
|
+
|
|
83
|
+
**Symptom:** Moving the cursor to a new file in a source directory caused 10–30 ms
|
|
84
|
+
of lag, making navigation feel sluggish.
|
|
85
|
+
|
|
86
|
+
**Root cause:** `SyntaxHighlighter#highlight` called `IO.popen(['bat', ...])` synchronously
|
|
87
|
+
inside the main loop. The bat process startup cost blocked the frame on every new file visit.
|
|
88
|
+
|
|
89
|
+
**Fix:** Added `highlight_async` which runs `bat` in a background Thread.
|
|
90
|
+
|
|
91
|
+
- The frame immediately after moving to a new file displays plain text (instant fallback)
|
|
92
|
+
- When the background thread completes, it sets `@highlight_updated = true`
|
|
93
|
+
- The main loop detects the flag and triggers a re-render with highlighting
|
|
94
|
+
- A pending guard prevents duplicate threads for the same file path
|
|
95
|
+
- A `Mutex` protects all cache reads/writes for thread safety
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Technical Details
|
|
100
|
+
|
|
101
|
+
### New Files
|
|
102
|
+
|
|
103
|
+
| File | Description |
|
|
104
|
+
|------|-------------|
|
|
105
|
+
| `lib/rufio/ansi_line_parser.rb` | Parses ANSI SGR escape sequences into token arrays. Full-width character-aware wrapping |
|
|
106
|
+
| `lib/rufio/syntax_highlighter.rb` | Wraps the `bat` command. mtime cache, async execution, Mutex protection |
|
|
107
|
+
| `test/test_ansi_line_parser.rb` | Unit tests for AnsiLineParser (25 tests) |
|
|
108
|
+
| `test/test_syntax_highlighter.rb` | Unit tests for SyntaxHighlighter (16 tests) |
|
|
109
|
+
|
|
110
|
+
### Modified Files
|
|
111
|
+
|
|
112
|
+
| File | Change |
|
|
113
|
+
|------|--------|
|
|
114
|
+
| `lib/rufio/renderer.rb` | Per-line `print` → single `write(buf)` + `flush` |
|
|
115
|
+
| `lib/rufio/terminal_ui.rb` | Added `@syntax_highlighter`, highlighting branch in `draw_file_preview_to_buffer`, `@highlight_updated` check in main loop |
|
|
116
|
+
| `lib/rufio/file_preview.rb` | Extended `determine_file_type` with Go, Rust, Shell, TOML, SQL, C, C++, Java, Dockerfile, Makefile |
|
|
117
|
+
| `lib/rufio/health_checker.rb` | Added `check_bat` method |
|
|
118
|
+
| `lib/rufio/config.rb` | Added `health.bat` message key (EN + JA) |
|
|
119
|
+
| `lib/rufio.rb` | Added `require` for `ansi_line_parser` and `syntax_highlighter` |
|
|
120
|
+
| `test/test_renderer.rb` | Added `OutputSpy` helper, 2 new atomic output tests |
|
|
121
|
+
|
|
122
|
+
### Architecture
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
bat (external process)
|
|
126
|
+
↓ IO.popen — background Thread
|
|
127
|
+
SyntaxHighlighter#highlight_async
|
|
128
|
+
↓ callback on completion
|
|
129
|
+
@preview_cache[path][:highlighted] = lines # store ANSI line array
|
|
130
|
+
@highlight_updated = true # notify main loop
|
|
131
|
+
↓ next frame
|
|
132
|
+
AnsiLineParser.parse(line) # ANSI → token array
|
|
133
|
+
AnsiLineParser.wrap(tokens, width) # full-width-aware wrapping
|
|
134
|
+
draw_highlighted_line_to_buffer(screen, ...) # per-char fg: color drawing
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Preview Cache Structure
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
@preview_cache[file_path] = {
|
|
141
|
+
content: Array<String>, # plain text lines
|
|
142
|
+
preview_data: Hash, # type, encoding, etc.
|
|
143
|
+
highlighted: nil | false | Array<String>,
|
|
144
|
+
# nil = not yet requested
|
|
145
|
+
# false = requested, awaiting background result
|
|
146
|
+
# Array = ANSI lines ready to render
|
|
147
|
+
wrapped: Hash, # width => wrapped plain lines
|
|
148
|
+
highlighted_wrapped: Hash # width => wrapped token arrays
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Tests
|
|
155
|
+
|
|
156
|
+
All tests pass (pre-existing TestUISnapshot / TestBufferParity snapshot mismatches excluded):
|
|
157
|
+
|
|
158
|
+
| Test file | Tests | Status |
|
|
159
|
+
|-----------|-------|--------|
|
|
160
|
+
| `test_ansi_line_parser.rb` | 25 | new |
|
|
161
|
+
| `test_syntax_highlighter.rb` | 16 (9 existing + 7 new) | pass |
|
|
162
|
+
| `test_renderer.rb` | 12 (10 existing + 2 new) | pass |
|
|
163
|
+
|
|
164
|
+
**34 new tests** added in this release.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Dependencies
|
|
169
|
+
|
|
170
|
+
### New Optional External Tool
|
|
171
|
+
|
|
172
|
+
| Tool | Purpose | Install |
|
|
173
|
+
|------|---------|---------|
|
|
174
|
+
| `bat` | Syntax highlighting | `brew install bat` / `apt install bat` |
|
|
175
|
+
|
|
176
|
+
rufio works normally without `bat` — plain text preview is always available as a fallback.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Health Check
|
|
181
|
+
|
|
182
|
+
Use `rufio -c` to verify `bat` installation:
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
rufio Health Check
|
|
186
|
+
✓ bat (syntax highlight): bat 0.25.0 (2024-...)
|
|
187
|
+
✗ bat (syntax highlight): not found
|
|
188
|
+
brew install bat # optional: enables syntax highlighting
|
|
189
|
+
```
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rufio
|
|
4
|
+
# ANSI エスケープコード付きの行をトークン列に分解するモジュール。
|
|
5
|
+
# bat --color=always の出力をシンタックスハイライト表示するために使用する。
|
|
6
|
+
module AnsiLineParser
|
|
7
|
+
# SGR (Select Graphic Rendition) ANSI エスケープシーケンスにマッチするパターン
|
|
8
|
+
ANSI_SGR_PATTERN = /\e\[[0-9;]*m/
|
|
9
|
+
|
|
10
|
+
# ANSI 付き行を {text: String, fg: String|nil} のトークン配列に分解する。
|
|
11
|
+
#
|
|
12
|
+
# @param line [String] ANSI コードを含む可能性がある行
|
|
13
|
+
# @return [Array<Hash>] {text:, fg:} のトークン配列
|
|
14
|
+
def self.parse(line)
|
|
15
|
+
tokens = []
|
|
16
|
+
current_fg = nil
|
|
17
|
+
|
|
18
|
+
# ANSI SGR シーケンス、非エスケープ文字列、孤立エスケープの順にスキャン
|
|
19
|
+
line.scan(/#{ANSI_SGR_PATTERN}|[^\e]+|\e(?!\[)/) do |part|
|
|
20
|
+
if part.start_with?("\e[")
|
|
21
|
+
current_fg = apply_ansi_sequence(current_fg, part)
|
|
22
|
+
else
|
|
23
|
+
tokens << { text: part, fg: current_fg }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
tokens
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# トークン配列の表示幅(ANSI コードを除く)を計算する。
|
|
31
|
+
#
|
|
32
|
+
# @param tokens [Array<Hash>] parse が返したトークン配列
|
|
33
|
+
# @return [Integer] 表示幅
|
|
34
|
+
def self.display_width(tokens)
|
|
35
|
+
tokens.sum { |t| TextUtils.display_width(t[:text]) }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# トークン配列を max_width で折り返し、行ごとのトークン配列の配列を返す。
|
|
39
|
+
# 全角文字(日本語等)は幅2として扱う。
|
|
40
|
+
#
|
|
41
|
+
# @param tokens [Array<Hash>] parse が返したトークン配列
|
|
42
|
+
# @param max_width [Integer] 折り返し幅(表示幅基準)
|
|
43
|
+
# @return [Array<Array<Hash>>] 行ごとのトークン配列
|
|
44
|
+
def self.wrap(tokens, max_width)
|
|
45
|
+
return [] if tokens.empty? || max_width <= 0
|
|
46
|
+
|
|
47
|
+
lines = []
|
|
48
|
+
current_line = []
|
|
49
|
+
current_width = 0
|
|
50
|
+
|
|
51
|
+
tokens.each do |token|
|
|
52
|
+
fg = token[:fg]
|
|
53
|
+
current_text = String.new
|
|
54
|
+
|
|
55
|
+
token[:text].each_char do |char|
|
|
56
|
+
char_w = TextUtils.char_width(char)
|
|
57
|
+
|
|
58
|
+
if current_width + char_w > max_width
|
|
59
|
+
# 折り返し: 現在のテキストをトークンとして確定
|
|
60
|
+
current_line << { text: current_text, fg: fg } unless current_text.empty?
|
|
61
|
+
lines << current_line
|
|
62
|
+
current_line = []
|
|
63
|
+
current_text = String.new
|
|
64
|
+
current_width = 0
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
current_text << char
|
|
68
|
+
current_width += char_w
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
current_line << { text: current_text, fg: fg } unless current_text.empty?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
lines << current_line unless current_line.empty?
|
|
75
|
+
lines
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# private helper: ANSI シーケンスを適用して新しい fg 状態を返す。
|
|
79
|
+
# リセットシーケンスは nil を返す。
|
|
80
|
+
# 複合シーケンス(例: \e[0;32m)はリセット後の色だけを返す。
|
|
81
|
+
def self.apply_ansi_sequence(current_fg, seq)
|
|
82
|
+
# \e[ と m の間のコード文字列を取り出す
|
|
83
|
+
codes_str = seq[2..-2]
|
|
84
|
+
codes = codes_str.split(';')
|
|
85
|
+
|
|
86
|
+
# すべてのコードがリセット("0" or "")ならリセット
|
|
87
|
+
if codes.empty? || codes.all? { |c| c.empty? || c == '0' }
|
|
88
|
+
return nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# 先頭がリセットコードで後続に色指定がある場合(例: \e[0;32m)
|
|
92
|
+
if codes.first.empty? || codes.first == '0'
|
|
93
|
+
remaining = codes.drop_while { |c| c.empty? || c == '0' }
|
|
94
|
+
return remaining.empty? ? nil : "\e[#{remaining.join(';')}m"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# 通常の色/属性コード → そのまま使用
|
|
98
|
+
seq
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private_class_method :apply_ansi_sequence
|
|
102
|
+
end
|
|
103
|
+
end
|
data/lib/rufio/command_mode.rb
CHANGED
|
@@ -28,7 +28,8 @@ module Rufio
|
|
|
28
28
|
@job_manager = job_manager
|
|
29
29
|
@script_runner = ScriptRunner.new(
|
|
30
30
|
script_paths: script_paths,
|
|
31
|
-
job_manager: job_manager
|
|
31
|
+
job_manager: job_manager,
|
|
32
|
+
command_logger: @background_executor&.command_logger
|
|
32
33
|
)
|
|
33
34
|
end
|
|
34
35
|
|
|
@@ -41,7 +42,8 @@ module Rufio
|
|
|
41
42
|
# ScriptRunnerも設定(ScriptPathManagerのパスを使用)
|
|
42
43
|
@script_runner = ScriptRunner.new(
|
|
43
44
|
script_paths: @script_path_manager.paths,
|
|
44
|
-
job_manager: job_manager
|
|
45
|
+
job_manager: job_manager,
|
|
46
|
+
command_logger: @background_executor&.command_logger
|
|
45
47
|
)
|
|
46
48
|
end
|
|
47
49
|
|
|
@@ -237,22 +239,27 @@ module Rufio
|
|
|
237
239
|
|
|
238
240
|
# スクリプトを実行する(@プレフィックス用)
|
|
239
241
|
# ScriptRunner → LocalScriptScanner の順にフォールバック
|
|
240
|
-
# @param
|
|
242
|
+
# @param script_input [String] スクリプト名(引数を含む場合あり)
|
|
241
243
|
# @param working_dir [String, nil] 作業ディレクトリ
|
|
242
244
|
# @return [String] 実行結果メッセージ
|
|
243
|
-
def execute_script(
|
|
245
|
+
def execute_script(script_input, working_dir)
|
|
244
246
|
working_dir ||= Dir.pwd
|
|
245
247
|
|
|
248
|
+
# スクリプト名と引数を分離(例: "retag.sh v0.70.0" → name="retag.sh", args="v0.70.0")
|
|
249
|
+
parts = script_input.split(' ', 2)
|
|
250
|
+
script_name = parts[0]
|
|
251
|
+
script_args = parts[1]
|
|
252
|
+
|
|
246
253
|
# ScriptRunnerで検索
|
|
247
254
|
if @script_runner
|
|
248
|
-
job = @script_runner.run(script_name, working_dir: working_dir)
|
|
255
|
+
job = @script_runner.run(script_name, working_dir: working_dir, args: script_args)
|
|
249
256
|
return "🚀 ジョブを開始: #{script_name}" if job
|
|
250
257
|
end
|
|
251
258
|
|
|
252
259
|
# LocalScriptScannerにフォールバック
|
|
253
260
|
local_script = @local_script_scanner.find_script(script_name)
|
|
254
261
|
if local_script
|
|
255
|
-
return execute_local_script(local_script, working_dir)
|
|
262
|
+
return execute_local_script(local_script, working_dir, script_args)
|
|
256
263
|
end
|
|
257
264
|
|
|
258
265
|
# どちらにも見つからない
|
|
@@ -266,33 +273,57 @@ module Rufio
|
|
|
266
273
|
# ローカルスクリプトを実行する
|
|
267
274
|
# @param script [Hash] スクリプト情報 { name:, path:, dir: }
|
|
268
275
|
# @param working_dir [String] 作業ディレクトリ
|
|
269
|
-
# @
|
|
270
|
-
|
|
276
|
+
# @param args [String, nil] スクリプトに渡す引数
|
|
277
|
+
# @return [String, Hash] 実行結果メッセージ
|
|
278
|
+
def execute_local_script(script, working_dir, args = nil)
|
|
279
|
+
command = build_script_command(script)
|
|
280
|
+
command = "#{command} #{args}" if args && !args.empty?
|
|
281
|
+
|
|
271
282
|
if @job_manager
|
|
272
283
|
job = @job_manager.add_job(
|
|
273
284
|
name: script[:name],
|
|
274
285
|
path: working_dir,
|
|
275
|
-
command:
|
|
286
|
+
command: command
|
|
276
287
|
)
|
|
277
288
|
job.start
|
|
278
289
|
|
|
279
290
|
Thread.new do
|
|
280
|
-
execute_script_in_background(job, script, working_dir)
|
|
291
|
+
execute_script_in_background(job, script, working_dir, command)
|
|
281
292
|
end
|
|
282
293
|
|
|
283
294
|
"🚀 ジョブを開始: #{script[:name]}"
|
|
284
295
|
else
|
|
285
296
|
# 同期実行
|
|
286
|
-
command = build_script_command(script)
|
|
287
297
|
stdout, stderr, status = Open3.capture3(command, chdir: working_dir)
|
|
288
|
-
{
|
|
298
|
+
result = {
|
|
289
299
|
success: status.success?,
|
|
290
300
|
output: stdout.strip,
|
|
291
301
|
stderr: stderr.strip
|
|
292
302
|
}
|
|
303
|
+
|
|
304
|
+
# Logsに記録
|
|
305
|
+
log_execution("@#{script[:name]}", result)
|
|
306
|
+
|
|
307
|
+
result
|
|
293
308
|
end
|
|
294
309
|
end
|
|
295
310
|
|
|
311
|
+
# 実行結果をCommandLoggerに記録
|
|
312
|
+
# @param command_name [String] コマンド名
|
|
313
|
+
# @param result [Hash] 実行結果 { success:, output:, stderr:, error: }
|
|
314
|
+
def log_execution(command_name, result)
|
|
315
|
+
logger = @background_executor&.command_logger
|
|
316
|
+
return unless logger
|
|
317
|
+
|
|
318
|
+
output = [result[:output], result[:stderr]].compact.reject(&:empty?).join("\n")
|
|
319
|
+
logger.log(
|
|
320
|
+
command_name,
|
|
321
|
+
output,
|
|
322
|
+
success: result[:success],
|
|
323
|
+
error: result[:error]
|
|
324
|
+
)
|
|
325
|
+
end
|
|
326
|
+
|
|
296
327
|
# スクリプトの実行コマンドを構築
|
|
297
328
|
# @param script [Hash] スクリプト情報
|
|
298
329
|
# @return [String] 実行コマンド
|
|
@@ -319,8 +350,7 @@ module Rufio
|
|
|
319
350
|
end
|
|
320
351
|
|
|
321
352
|
# ローカルスクリプトをバックグラウンドで実行
|
|
322
|
-
def execute_script_in_background(job, script, working_dir)
|
|
323
|
-
command = build_script_command(script)
|
|
353
|
+
def execute_script_in_background(job, script, working_dir, command)
|
|
324
354
|
stdout, stderr, status = Open3.capture3(command, chdir: working_dir)
|
|
325
355
|
|
|
326
356
|
job.append_log(stdout) unless stdout.empty?
|
|
@@ -332,10 +362,18 @@ module Rufio
|
|
|
332
362
|
job.fail(exit_code: status.exitstatus)
|
|
333
363
|
end
|
|
334
364
|
|
|
365
|
+
# Logsに記録
|
|
366
|
+
log_execution("@#{script[:name]}", {
|
|
367
|
+
success: status.success?,
|
|
368
|
+
output: stdout.strip,
|
|
369
|
+
stderr: stderr.strip
|
|
370
|
+
})
|
|
371
|
+
|
|
335
372
|
@job_manager&.notify_completion(job)
|
|
336
373
|
rescue StandardError => e
|
|
337
374
|
job.append_log("Error: #{e.message}")
|
|
338
375
|
job.fail(exit_code: -1)
|
|
376
|
+
log_execution("@#{script[:name]}", { success: false, output: '', stderr: e.message })
|
|
339
377
|
@job_manager&.notify_completion(job)
|
|
340
378
|
end
|
|
341
379
|
|
|
@@ -368,11 +406,18 @@ module Rufio
|
|
|
368
406
|
result[:error] = "コマンドが失敗しました (終了コード: #{status.exitstatus})"
|
|
369
407
|
end
|
|
370
408
|
|
|
409
|
+
# Logsに記録
|
|
410
|
+
log_execution("rake:#{task_name}", result)
|
|
411
|
+
|
|
371
412
|
result
|
|
372
413
|
rescue Errno::ENOENT => e
|
|
373
|
-
{ success: false, error: "rakeが見つかりません: #{e.message}" }
|
|
414
|
+
result = { success: false, error: "rakeが見つかりません: #{e.message}" }
|
|
415
|
+
log_execution("rake:#{task_name}", result)
|
|
416
|
+
result
|
|
374
417
|
rescue StandardError => e
|
|
375
|
-
{ success: false, error: "rake実行エラー: #{e.message}" }
|
|
418
|
+
result = { success: false, error: "rake実行エラー: #{e.message}" }
|
|
419
|
+
log_execution("rake:#{task_name}", result)
|
|
420
|
+
result
|
|
376
421
|
end
|
|
377
422
|
end
|
|
378
423
|
|
data/lib/rufio/config.rb
CHANGED
|
@@ -66,6 +66,7 @@ module Rufio
|
|
|
66
66
|
'health.fzf' => 'fzf (file search)',
|
|
67
67
|
'health.rga' => 'rga (content search)',
|
|
68
68
|
'health.zoxide' => 'zoxide (directory history)',
|
|
69
|
+
'health.bat' => 'bat (syntax highlight)',
|
|
69
70
|
'health.file_opener' => 'System file opener',
|
|
70
71
|
'health.summary' => 'Summary:',
|
|
71
72
|
'health.ok' => 'OK',
|
|
@@ -135,6 +136,7 @@ module Rufio
|
|
|
135
136
|
'health.fzf' => 'fzf (file search)',
|
|
136
137
|
'health.rga' => 'rga (content search)',
|
|
137
138
|
'health.zoxide' => 'zoxide (directory history)',
|
|
139
|
+
'health.bat' => 'bat (syntax highlight)',
|
|
138
140
|
'health.file_opener' => 'System file opener',
|
|
139
141
|
'health.summary' => 'Summary:',
|
|
140
142
|
'health.ok' => 'OK',
|
data/lib/rufio/file_preview.rb
CHANGED
|
@@ -127,7 +127,16 @@ module Rufio
|
|
|
127
127
|
|
|
128
128
|
def determine_file_type(file_path)
|
|
129
129
|
extension = File.extname(file_path).downcase
|
|
130
|
-
|
|
130
|
+
basename = File.basename(file_path)
|
|
131
|
+
|
|
132
|
+
# 拡張子なしファイル名での判定(Dockerfile 等)
|
|
133
|
+
case basename
|
|
134
|
+
when "Dockerfile", /\ADockerfile\./
|
|
135
|
+
return { type: "code", language: "dockerfile" }
|
|
136
|
+
when "Makefile", "GNUmakefile"
|
|
137
|
+
return { type: "code", language: "makefile" }
|
|
138
|
+
end
|
|
139
|
+
|
|
131
140
|
case extension
|
|
132
141
|
when ".rb"
|
|
133
142
|
{ type: "code", language: "ruby" }
|
|
@@ -147,6 +156,22 @@ module Rufio
|
|
|
147
156
|
{ type: "code", language: "yaml" }
|
|
148
157
|
when ".md", ".markdown"
|
|
149
158
|
{ type: "code", language: "markdown" }
|
|
159
|
+
when ".go"
|
|
160
|
+
{ type: "code", language: "go" }
|
|
161
|
+
when ".rs"
|
|
162
|
+
{ type: "code", language: "rust" }
|
|
163
|
+
when ".sh", ".bash", ".zsh"
|
|
164
|
+
{ type: "code", language: "shell" }
|
|
165
|
+
when ".toml"
|
|
166
|
+
{ type: "code", language: "toml" }
|
|
167
|
+
when ".sql"
|
|
168
|
+
{ type: "code", language: "sql" }
|
|
169
|
+
when ".c", ".h"
|
|
170
|
+
{ type: "code", language: "c" }
|
|
171
|
+
when ".cpp", ".cc", ".cxx", ".hpp"
|
|
172
|
+
{ type: "code", language: "cpp" }
|
|
173
|
+
when ".java"
|
|
174
|
+
{ type: "code", language: "java" }
|
|
150
175
|
when ".txt", ".log"
|
|
151
176
|
{ type: "text", language: nil }
|
|
152
177
|
when ".zip", ".tar", ".gz", ".bz2", ".xz", ".7z"
|
data/lib/rufio/health_checker.rb
CHANGED
|
@@ -21,6 +21,7 @@ module Rufio
|
|
|
21
21
|
{ name: ConfigLoader.message('health.fzf'), method: :check_fzf },
|
|
22
22
|
{ name: ConfigLoader.message('health.rga'), method: :check_rga },
|
|
23
23
|
{ name: ConfigLoader.message('health.zoxide'), method: :check_zoxide },
|
|
24
|
+
{ name: ConfigLoader.message('health.bat'), method: :check_bat },
|
|
24
25
|
{ name: ConfigLoader.message('health.file_opener'), method: :check_file_opener }
|
|
25
26
|
]
|
|
26
27
|
|
|
@@ -136,6 +137,23 @@ module Rufio
|
|
|
136
137
|
end
|
|
137
138
|
end
|
|
138
139
|
|
|
140
|
+
def check_bat
|
|
141
|
+
if system("which bat > /dev/null 2>&1")
|
|
142
|
+
version = `bat --version 2>/dev/null`.strip
|
|
143
|
+
{
|
|
144
|
+
status: :ok,
|
|
145
|
+
message: version,
|
|
146
|
+
details: nil
|
|
147
|
+
}
|
|
148
|
+
else
|
|
149
|
+
{
|
|
150
|
+
status: :warning,
|
|
151
|
+
message: "bat #{ConfigLoader.message('health.tool_not_found')}",
|
|
152
|
+
details: install_instruction_for('bat')
|
|
153
|
+
}
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
139
157
|
def check_file_opener
|
|
140
158
|
case RUBY_PLATFORM
|
|
141
159
|
when /darwin/
|
|
@@ -180,6 +198,8 @@ module Rufio
|
|
|
180
198
|
"#{ConfigLoader.message('health.install_brew')} rga"
|
|
181
199
|
when 'zoxide'
|
|
182
200
|
"#{ConfigLoader.message('health.install_brew')} zoxide"
|
|
201
|
+
when 'bat'
|
|
202
|
+
"#{ConfigLoader.message('health.install_brew')} bat # optional: syntax highlight"
|
|
183
203
|
end
|
|
184
204
|
when /linux/
|
|
185
205
|
case tool
|
|
@@ -189,6 +209,8 @@ module Rufio
|
|
|
189
209
|
ConfigLoader.message('health.rga_releases')
|
|
190
210
|
when 'zoxide'
|
|
191
211
|
"#{ConfigLoader.message('health.install_apt')} zoxide (Ubuntu/Debian) or check your package manager"
|
|
212
|
+
when 'bat'
|
|
213
|
+
"#{ConfigLoader.message('health.install_apt')} bat (Ubuntu/Debian) or check your package manager # optional: syntax highlight"
|
|
192
214
|
end
|
|
193
215
|
else
|
|
194
216
|
ConfigLoader.message('health.install_guide')
|
|
@@ -218,10 +218,15 @@ module Rufio
|
|
|
218
218
|
end
|
|
219
219
|
|
|
220
220
|
def get_active_entries
|
|
221
|
-
if @filter_manager.filter_active?
|
|
222
|
-
|
|
221
|
+
entries = if @filter_manager.filter_active?
|
|
222
|
+
@filter_manager.filtered_entries
|
|
223
|
+
else
|
|
224
|
+
@directory_listing&.list_entries || []
|
|
225
|
+
end
|
|
226
|
+
if @in_help_mode || @in_log_viewer_mode
|
|
227
|
+
entries.reject { |e| e[:name] == '..' }
|
|
223
228
|
else
|
|
224
|
-
|
|
229
|
+
entries
|
|
225
230
|
end
|
|
226
231
|
end
|
|
227
232
|
|
data/lib/rufio/renderer.rb
CHANGED
|
@@ -31,13 +31,17 @@ module Rufio
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# Phase1: Only process dirty rows (rows that have changed)
|
|
34
|
+
# 全dirty rowsの出力を1つのバッファに積んでから単一の write() で書き出す。
|
|
35
|
+
# STDOUT sync=true 環境で print を行ごとに呼ぶと各行で即座にフラッシュされ
|
|
36
|
+
# 中間状態が表示されてちらつきが発生するため、アトミックな更新を保証する。
|
|
37
|
+
buf = String.new("")
|
|
34
38
|
rendered_count = 0
|
|
35
39
|
dirty.each do |y|
|
|
36
40
|
line = screen.row(y)
|
|
37
41
|
next if line == @front[y] # Skip if content is actually the same
|
|
38
42
|
|
|
39
|
-
# Move cursor to line y (1-indexed) and
|
|
40
|
-
|
|
43
|
+
# Move cursor to line y (1-indexed) and buffer the line
|
|
44
|
+
buf << "\e[#{y + 1};1H#{line}"
|
|
41
45
|
@front[y] = line
|
|
42
46
|
rendered_count += 1
|
|
43
47
|
end
|
|
@@ -45,8 +49,11 @@ module Rufio
|
|
|
45
49
|
# Phase1: Clear dirty tracking after rendering
|
|
46
50
|
screen.clear_dirty
|
|
47
51
|
|
|
48
|
-
#
|
|
49
|
-
|
|
52
|
+
# 単一の write() でアトミックに出力し、その後 flush する
|
|
53
|
+
if rendered_count > 0
|
|
54
|
+
@output.write(buf)
|
|
55
|
+
@output.flush
|
|
56
|
+
end
|
|
50
57
|
|
|
51
58
|
true
|
|
52
59
|
end
|
data/lib/rufio/screen.rb
CHANGED
|
@@ -114,7 +114,8 @@ module Rufio
|
|
|
114
114
|
return " " * @width if y < 0 || y >= @height
|
|
115
115
|
|
|
116
116
|
# Phase1: Pre-allocate string capacity for better performance
|
|
117
|
-
|
|
117
|
+
# String.new("", capacity: N) inherits UTF-8 encoding from ""; String.new(capacity: N) creates ASCII-8BIT
|
|
118
|
+
result = String.new("", capacity: @width * 20)
|
|
118
119
|
current_width = 0 # Phase1: Accumulate width from cells (no recalculation)
|
|
119
120
|
|
|
120
121
|
# オーバーレイがある場合はベース + オーバーレイを合成
|
|
@@ -281,7 +282,7 @@ module Rufio
|
|
|
281
282
|
return char if fg.nil? && bg.nil?
|
|
282
283
|
|
|
283
284
|
# Phase1: String builder with pre-allocated capacity (no array generation)
|
|
284
|
-
result = String.new(capacity: 30)
|
|
285
|
+
result = String.new("", capacity: 30)
|
|
285
286
|
result << fg if fg
|
|
286
287
|
result << bg if bg
|
|
287
288
|
result << char
|
data/lib/rufio/script_runner.rb
CHANGED
|
@@ -11,9 +11,11 @@ module Rufio
|
|
|
11
11
|
|
|
12
12
|
# @param script_paths [Array<String>] スクリプトを検索するディレクトリのリスト
|
|
13
13
|
# @param job_manager [JobManager, nil] ジョブマネージャー(nilの場合は同期実行)
|
|
14
|
-
|
|
14
|
+
# @param command_logger [CommandLogger, nil] コマンドロガー
|
|
15
|
+
def initialize(script_paths:, job_manager: nil, command_logger: nil)
|
|
15
16
|
@script_paths = script_paths.map { |p| File.expand_path(p) }
|
|
16
17
|
@job_manager = job_manager
|
|
18
|
+
@command_logger = command_logger
|
|
17
19
|
@scripts_cache = nil
|
|
18
20
|
end
|
|
19
21
|
|
|
@@ -54,15 +56,16 @@ module Rufio
|
|
|
54
56
|
# スクリプトをジョブとして実行
|
|
55
57
|
# @param name [String] スクリプト名
|
|
56
58
|
# @param working_dir [String] 作業ディレクトリ
|
|
59
|
+
# @param args [String, nil] スクリプトに渡す引数
|
|
57
60
|
# @param selected_file [String, nil] 選択中のファイル
|
|
58
61
|
# @param selected_dir [String, nil] 選択中のディレクトリ
|
|
59
62
|
# @return [TaskStatus, nil] 作成されたジョブ、またはスクリプトが見つからない場合nil
|
|
60
|
-
def run(name, working_dir:, selected_file: nil, selected_dir: nil)
|
|
63
|
+
def run(name, working_dir:, args: nil, selected_file: nil, selected_dir: nil)
|
|
61
64
|
script = find_script(name)
|
|
62
65
|
return nil unless script
|
|
63
66
|
|
|
64
67
|
env = build_environment(working_dir, selected_file, selected_dir)
|
|
65
|
-
execute_script(script, working_dir, env)
|
|
68
|
+
execute_script(script, working_dir, env, args)
|
|
66
69
|
end
|
|
67
70
|
|
|
68
71
|
# キャッシュをクリア
|
|
@@ -130,9 +133,11 @@ module Rufio
|
|
|
130
133
|
# @param script [Hash] スクリプト情報
|
|
131
134
|
# @param working_dir [String] 作業ディレクトリ
|
|
132
135
|
# @param env [Hash] 環境変数
|
|
136
|
+
# @param args [String, nil] スクリプトに渡す引数
|
|
133
137
|
# @return [TaskStatus] 作成されたジョブ
|
|
134
|
-
def execute_script(script, working_dir, env = {})
|
|
138
|
+
def execute_script(script, working_dir, env = {}, args = nil)
|
|
135
139
|
command = build_command(script)
|
|
140
|
+
command = "#{command} #{args}" if args && !args.empty?
|
|
136
141
|
|
|
137
142
|
if @job_manager
|
|
138
143
|
# ジョブマネージャーにジョブを追加
|
|
@@ -205,12 +210,24 @@ module Rufio
|
|
|
205
210
|
job.fail(exit_code: status.exitstatus)
|
|
206
211
|
end
|
|
207
212
|
|
|
213
|
+
# CommandLoggerに記録
|
|
214
|
+
log_to_command_logger(job.name, stdout, stderr, status.success?)
|
|
215
|
+
|
|
208
216
|
# 通知を送信
|
|
209
217
|
@job_manager&.notify_completion(job)
|
|
210
218
|
rescue StandardError => e
|
|
211
219
|
job.append_log("Error: #{e.message}")
|
|
212
220
|
job.fail(exit_code: -1)
|
|
221
|
+
log_to_command_logger(job.name, '', e.message, false)
|
|
213
222
|
@job_manager&.notify_completion(job)
|
|
214
223
|
end
|
|
224
|
+
|
|
225
|
+
# CommandLoggerに実行結果を記録
|
|
226
|
+
def log_to_command_logger(name, stdout, stderr, success)
|
|
227
|
+
return unless @command_logger
|
|
228
|
+
|
|
229
|
+
output = [stdout, stderr].compact.map(&:strip).reject(&:empty?).join("\n")
|
|
230
|
+
@command_logger.log("@#{name}", output, success: success)
|
|
231
|
+
end
|
|
215
232
|
end
|
|
216
233
|
end
|
|
@@ -17,6 +17,12 @@ module Rufio
|
|
|
17
17
|
def toggle_selection(entry, current_directory = nil)
|
|
18
18
|
return false unless entry
|
|
19
19
|
|
|
20
|
+
# 異なるディレクトリで選択した場合、古い選択をクリアしてリセット
|
|
21
|
+
if current_directory && @source_directory && current_directory != @source_directory
|
|
22
|
+
@selected_items.clear
|
|
23
|
+
@source_directory = nil
|
|
24
|
+
end
|
|
25
|
+
|
|
20
26
|
if @selected_items.include?(entry[:name])
|
|
21
27
|
@selected_items.delete(entry[:name])
|
|
22
28
|
# Clear source_directory if no items are selected
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rufio
|
|
4
|
+
# bat コマンドを使ってファイルのシンタックスハイライトを行うクラス。
|
|
5
|
+
# bat が存在しない場合は available? が false を返し、highlight は [] を返す。
|
|
6
|
+
# mtime ベースのキャッシュを持ち、同じファイルの2回目以降は0msで返す。
|
|
7
|
+
class SyntaxHighlighter
|
|
8
|
+
def initialize
|
|
9
|
+
@bat_available = bat_available?
|
|
10
|
+
@cache = {} # file_path => { mtime: Time, lines: Array<String> }
|
|
11
|
+
@pending = {} # file_path => true(非同期実行中フラグ)
|
|
12
|
+
@mutex = Mutex.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# bat コマンドが利用可能かどうか
|
|
16
|
+
#
|
|
17
|
+
# @return [Boolean]
|
|
18
|
+
def available?
|
|
19
|
+
@bat_available
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# ファイルをシンタックスハイライトして ANSI 付き行の配列を返す(同期版)。
|
|
23
|
+
# bat が使えない場合、ファイルが存在しない場合は []。
|
|
24
|
+
#
|
|
25
|
+
# @param file_path [String] ハイライト対象のファイルパス
|
|
26
|
+
# @param max_lines [Integer] 取得する最大行数
|
|
27
|
+
# @return [Array<String>] ANSI エスケープコード付きの行配列
|
|
28
|
+
def highlight(file_path, max_lines: 50)
|
|
29
|
+
return [] unless @bat_available
|
|
30
|
+
return [] unless File.exist?(file_path)
|
|
31
|
+
|
|
32
|
+
mtime = File.mtime(file_path)
|
|
33
|
+
@mutex.synchronize do
|
|
34
|
+
cache = @cache[file_path]
|
|
35
|
+
return cache[:lines] if cache && cache[:mtime] == mtime
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
lines = run_bat(file_path, max_lines)
|
|
39
|
+
@mutex.synchronize { @cache[file_path] = { mtime: mtime, lines: lines } }
|
|
40
|
+
lines
|
|
41
|
+
rescue => _e
|
|
42
|
+
[]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# ファイルをバックグラウンドスレッドでシンタックスハイライトする(非同期版)。
|
|
46
|
+
# メインループをブロックせず、bat 完了時にコールバックを呼ぶ。
|
|
47
|
+
# キャッシュヒット時はコールバックを即時(同期的に)呼ぶ。
|
|
48
|
+
# 同一ファイルへの重複呼び出しは無視する(ペンディングガード)。
|
|
49
|
+
#
|
|
50
|
+
# @param file_path [String] ハイライト対象のファイルパス
|
|
51
|
+
# @param max_lines [Integer] 取得する最大行数
|
|
52
|
+
# @yieldparam lines [Array<String>] ANSI付き行配列(エラー時は [])
|
|
53
|
+
def highlight_async(file_path, max_lines: 50, &callback)
|
|
54
|
+
return unless @bat_available
|
|
55
|
+
return unless File.exist?(file_path)
|
|
56
|
+
|
|
57
|
+
mtime = File.mtime(file_path)
|
|
58
|
+
|
|
59
|
+
@mutex.synchronize do
|
|
60
|
+
# キャッシュヒット → 即時コールバック
|
|
61
|
+
cache = @cache[file_path]
|
|
62
|
+
if cache && cache[:mtime] == mtime
|
|
63
|
+
callback&.call(cache[:lines])
|
|
64
|
+
return
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# 既にペンディング中 → 重複スレッドを立てない
|
|
68
|
+
return if @pending[file_path]
|
|
69
|
+
|
|
70
|
+
@pending[file_path] = true
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
Thread.new do
|
|
74
|
+
begin
|
|
75
|
+
lines = run_bat(file_path, max_lines)
|
|
76
|
+
@mutex.synchronize do
|
|
77
|
+
@cache[file_path] = { mtime: mtime, lines: lines }
|
|
78
|
+
@pending.delete(file_path)
|
|
79
|
+
end
|
|
80
|
+
callback&.call(lines)
|
|
81
|
+
rescue => _e
|
|
82
|
+
@mutex.synchronize { @pending.delete(file_path) }
|
|
83
|
+
callback&.call([])
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def bat_available?
|
|
93
|
+
system('which bat > /dev/null 2>&1')
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def run_bat(file_path, max_lines)
|
|
97
|
+
output = IO.popen(
|
|
98
|
+
['bat', '--color=always', '--plain', '--line-range', "1:#{max_lines}", file_path],
|
|
99
|
+
err: File::NULL
|
|
100
|
+
) { |io| io.read }
|
|
101
|
+
|
|
102
|
+
return [] unless output
|
|
103
|
+
|
|
104
|
+
output
|
|
105
|
+
.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
|
|
106
|
+
.split("\n")
|
|
107
|
+
rescue => _e
|
|
108
|
+
[]
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
data/lib/rufio/terminal_ui.rb
CHANGED
|
@@ -66,6 +66,11 @@ module Rufio
|
|
|
66
66
|
@preview_cache = {}
|
|
67
67
|
@last_preview_path = nil
|
|
68
68
|
|
|
69
|
+
# シンタックスハイライター(bat が利用可能な場合のみ動作)
|
|
70
|
+
@syntax_highlighter = SyntaxHighlighter.new
|
|
71
|
+
# 非同期ハイライト完了フラグ(Thread → メインループへの通知)
|
|
72
|
+
@highlight_updated = false
|
|
73
|
+
|
|
69
74
|
# Footer cache (bookmark list)
|
|
70
75
|
@cached_bookmarks = nil
|
|
71
76
|
@cached_bookmark_time = nil
|
|
@@ -216,6 +221,7 @@ module Rufio
|
|
|
216
221
|
last_frame_time = Time.now
|
|
217
222
|
current_fps = 0.0
|
|
218
223
|
last_fps_update = Time.now
|
|
224
|
+
@last_displayed_fps = 0.0
|
|
219
225
|
|
|
220
226
|
# 再描画フラグ
|
|
221
227
|
needs_redraw = false
|
|
@@ -237,8 +243,11 @@ module Rufio
|
|
|
237
243
|
last_fps_update = start
|
|
238
244
|
end
|
|
239
245
|
|
|
240
|
-
#
|
|
241
|
-
|
|
246
|
+
# FPS表示の更新タイミングで再描画(1秒ごと)
|
|
247
|
+
if current_fps != @last_displayed_fps
|
|
248
|
+
@last_displayed_fps = current_fps
|
|
249
|
+
needs_redraw = true
|
|
250
|
+
end
|
|
242
251
|
end
|
|
243
252
|
|
|
244
253
|
# UPDATE phase - ノンブロッキング入力処理
|
|
@@ -294,6 +303,12 @@ module Rufio
|
|
|
294
303
|
needs_redraw = true
|
|
295
304
|
end
|
|
296
305
|
|
|
306
|
+
# 非同期シンタックスハイライト完了チェック(バックグラウンドスレッドからの通知)
|
|
307
|
+
if @highlight_updated
|
|
308
|
+
@highlight_updated = false
|
|
309
|
+
needs_redraw = true
|
|
310
|
+
end
|
|
311
|
+
|
|
297
312
|
# DRAW & RENDER phase - 変更があった場合のみ描画
|
|
298
313
|
if needs_redraw
|
|
299
314
|
# Screenバッファに描画(clearは呼ばない。必要な部分だけ更新)
|
|
@@ -825,14 +840,19 @@ module Rufio
|
|
|
825
840
|
# プレビューコンテンツをキャッシュから取得(毎フレームのファイルI/Oを回避)
|
|
826
841
|
preview_content = nil
|
|
827
842
|
wrapped_lines = nil
|
|
843
|
+
highlighted_wrapped_lines = nil
|
|
828
844
|
|
|
829
845
|
if selected_entry && selected_entry[:type] == 'file'
|
|
830
846
|
# キャッシュチェック: 選択ファイルが変わった場合のみプレビューを更新
|
|
831
847
|
if @last_preview_path != selected_entry[:path]
|
|
832
|
-
|
|
848
|
+
full_preview = @file_preview.preview_file(selected_entry[:path])
|
|
849
|
+
preview_content = extract_preview_lines(full_preview)
|
|
833
850
|
@preview_cache[selected_entry[:path]] = {
|
|
834
851
|
content: preview_content,
|
|
835
|
-
|
|
852
|
+
preview_data: full_preview,
|
|
853
|
+
highlighted: nil, # nil = 未取得
|
|
854
|
+
wrapped: {},
|
|
855
|
+
highlighted_wrapped: {}
|
|
836
856
|
}
|
|
837
857
|
@last_preview_path = selected_entry[:path]
|
|
838
858
|
else
|
|
@@ -841,8 +861,49 @@ module Rufio
|
|
|
841
861
|
preview_content = cache_entry[:content] if cache_entry
|
|
842
862
|
end
|
|
843
863
|
|
|
844
|
-
#
|
|
845
|
-
if
|
|
864
|
+
# bat が利用可能な場合はシンタックスハイライトを取得(非同期)
|
|
865
|
+
if @syntax_highlighter&.available? && preview_content
|
|
866
|
+
cache_entry = @preview_cache[selected_entry[:path]]
|
|
867
|
+
if cache_entry
|
|
868
|
+
preview_data = cache_entry[:preview_data]
|
|
869
|
+
if preview_data && preview_data[:type] == 'code' && preview_data[:encoding] == 'UTF-8'
|
|
870
|
+
# ハイライト行を未取得なら非同期で bat を呼び出す
|
|
871
|
+
# nil = 未リクエスト、false = リクエスト済み(結果待ち)、Array = 取得済み
|
|
872
|
+
if cache_entry[:highlighted].nil?
|
|
873
|
+
# 即座に false をセットしてペンディング状態にする(重複リクエスト防止)
|
|
874
|
+
cache_entry[:highlighted] = false
|
|
875
|
+
file_path = selected_entry[:path]
|
|
876
|
+
@syntax_highlighter.highlight_async(file_path) do |lines|
|
|
877
|
+
# バックグラウンドスレッドからキャッシュを更新
|
|
878
|
+
if (ce = @preview_cache[file_path])
|
|
879
|
+
ce[:highlighted] = lines
|
|
880
|
+
ce[:highlighted_wrapped] = {} # 折り返しキャッシュをクリア
|
|
881
|
+
end
|
|
882
|
+
@highlight_updated = true # メインループに再描画を通知
|
|
883
|
+
end
|
|
884
|
+
# このフレームはプレーンテキストで表示(次フレームでハイライト表示)
|
|
885
|
+
end
|
|
886
|
+
|
|
887
|
+
highlighted = cache_entry[:highlighted]
|
|
888
|
+
if highlighted.is_a?(Array) && !highlighted.empty? && safe_width > 0
|
|
889
|
+
if cache_entry[:highlighted_wrapped][safe_width]
|
|
890
|
+
highlighted_wrapped_lines = cache_entry[:highlighted_wrapped][safe_width]
|
|
891
|
+
else
|
|
892
|
+
# 各ハイライト行をトークン化して折り返す
|
|
893
|
+
hl_wrapped = highlighted.flat_map do |hl_line|
|
|
894
|
+
tokens = AnsiLineParser.parse(hl_line)
|
|
895
|
+
tokens.empty? ? [[]] : AnsiLineParser.wrap(tokens, safe_width - 1)
|
|
896
|
+
end
|
|
897
|
+
cache_entry[:highlighted_wrapped][safe_width] = hl_wrapped
|
|
898
|
+
highlighted_wrapped_lines = hl_wrapped
|
|
899
|
+
end
|
|
900
|
+
end
|
|
901
|
+
end
|
|
902
|
+
end
|
|
903
|
+
end
|
|
904
|
+
|
|
905
|
+
# プレーンテキストの折り返し(ハイライトなしのフォールバック)
|
|
906
|
+
if preview_content && safe_width > 0 && highlighted_wrapped_lines.nil?
|
|
846
907
|
cache_entry = @preview_cache[selected_entry[:path]]
|
|
847
908
|
if cache_entry && cache_entry[:wrapped][safe_width]
|
|
848
909
|
wrapped_lines = cache_entry[:wrapped][safe_width]
|
|
@@ -853,48 +914,55 @@ module Rufio
|
|
|
853
914
|
end
|
|
854
915
|
end
|
|
855
916
|
|
|
917
|
+
content_x = cursor_position + 1
|
|
918
|
+
|
|
856
919
|
(0...height).each do |i|
|
|
857
920
|
line_num = i + CONTENT_START_LINE
|
|
858
921
|
|
|
859
922
|
# 区切り線
|
|
860
923
|
screen.put(cursor_position, line_num, '│')
|
|
861
924
|
|
|
862
|
-
|
|
925
|
+
next if safe_width <= 0
|
|
863
926
|
|
|
864
927
|
if selected_entry && i == 0
|
|
865
928
|
# プレビューヘッダー
|
|
866
929
|
header = " #{selected_entry[:name]} "
|
|
867
|
-
if @keybind_handler&.preview_focused?
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
930
|
+
header += "[PREVIEW MODE]" if @keybind_handler&.preview_focused?
|
|
931
|
+
header = TextUtils.truncate_to_width(header, safe_width) if TextUtils.display_width(header) > safe_width
|
|
932
|
+
remaining_space = safe_width - TextUtils.display_width(header)
|
|
933
|
+
header += ' ' * remaining_space if remaining_space > 0
|
|
934
|
+
screen.put_string(content_x, line_num, header)
|
|
935
|
+
|
|
936
|
+
elsif i >= 2 && highlighted_wrapped_lines
|
|
937
|
+
# シンタックスハイライト付きコンテンツ
|
|
873
938
|
scroll_offset = @keybind_handler&.preview_scroll_offset || 0
|
|
874
939
|
display_line_index = i - 2 + scroll_offset
|
|
875
940
|
|
|
876
|
-
if display_line_index <
|
|
877
|
-
|
|
878
|
-
|
|
941
|
+
if display_line_index < highlighted_wrapped_lines.length
|
|
942
|
+
draw_highlighted_line_to_buffer(screen, content_x, line_num,
|
|
943
|
+
highlighted_wrapped_lines[display_line_index], safe_width)
|
|
879
944
|
else
|
|
880
|
-
|
|
945
|
+
screen.put_string(content_x, line_num, ' ' * safe_width)
|
|
881
946
|
end
|
|
882
|
-
else
|
|
883
|
-
content_to_print = ' '
|
|
884
|
-
end
|
|
885
|
-
|
|
886
|
-
# safe_widthを超えないよう切り詰め
|
|
887
|
-
next if safe_width <= 0
|
|
888
947
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
948
|
+
elsif i >= 2 && wrapped_lines
|
|
949
|
+
# プレーンテキストコンテンツ
|
|
950
|
+
scroll_offset = @keybind_handler&.preview_scroll_offset || 0
|
|
951
|
+
display_line_index = i - 2 + scroll_offset
|
|
892
952
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
953
|
+
content_to_print = if display_line_index < wrapped_lines.length
|
|
954
|
+
" #{wrapped_lines[display_line_index] || ''}"
|
|
955
|
+
else
|
|
956
|
+
' '
|
|
957
|
+
end
|
|
958
|
+
content_to_print = TextUtils.truncate_to_width(content_to_print, safe_width) if TextUtils.display_width(content_to_print) > safe_width
|
|
959
|
+
remaining_space = safe_width - TextUtils.display_width(content_to_print)
|
|
960
|
+
content_to_print += ' ' * remaining_space if remaining_space > 0
|
|
961
|
+
screen.put_string(content_x, line_num, content_to_print)
|
|
896
962
|
|
|
897
|
-
|
|
963
|
+
else
|
|
964
|
+
screen.put_string(content_x, line_num, ' ' * safe_width)
|
|
965
|
+
end
|
|
898
966
|
end
|
|
899
967
|
end
|
|
900
968
|
|
|
@@ -990,6 +1058,13 @@ module Rufio
|
|
|
990
1058
|
return [] unless entry && entry[:type] == 'file'
|
|
991
1059
|
|
|
992
1060
|
preview = @file_preview.preview_file(entry[:path])
|
|
1061
|
+
extract_preview_lines(preview)
|
|
1062
|
+
rescue StandardError
|
|
1063
|
+
["(#{ConfigLoader.message('file.preview_error')})"]
|
|
1064
|
+
end
|
|
1065
|
+
|
|
1066
|
+
# FilePreview の結果ハッシュからプレーンテキスト行を抽出する
|
|
1067
|
+
def extract_preview_lines(preview)
|
|
993
1068
|
case preview[:type]
|
|
994
1069
|
when 'text', 'code'
|
|
995
1070
|
preview[:lines]
|
|
@@ -1004,6 +1079,36 @@ module Rufio
|
|
|
1004
1079
|
["(#{ConfigLoader.message('file.preview_error')})"]
|
|
1005
1080
|
end
|
|
1006
1081
|
|
|
1082
|
+
# ハイライト済みトークン列を1行分 Screen バッファに描画する
|
|
1083
|
+
# 先頭に1スペースを追加し、残りをスペースで埋める
|
|
1084
|
+
def draw_highlighted_line_to_buffer(screen, x, y, tokens, max_width)
|
|
1085
|
+
current_x = x
|
|
1086
|
+
max_x = x + max_width
|
|
1087
|
+
|
|
1088
|
+
# 先頭スペース
|
|
1089
|
+
if current_x < max_x
|
|
1090
|
+
screen.put(current_x, y, ' ')
|
|
1091
|
+
current_x += 1
|
|
1092
|
+
end
|
|
1093
|
+
|
|
1094
|
+
# トークンを描画
|
|
1095
|
+
tokens&.each do |token|
|
|
1096
|
+
break if current_x >= max_x
|
|
1097
|
+
token[:text].each_char do |char|
|
|
1098
|
+
char_w = TextUtils.char_width(char)
|
|
1099
|
+
break if current_x + char_w > max_x
|
|
1100
|
+
screen.put(current_x, y, char, fg: token[:fg])
|
|
1101
|
+
current_x += char_w
|
|
1102
|
+
end
|
|
1103
|
+
end
|
|
1104
|
+
|
|
1105
|
+
# 残りをスペースで埋める
|
|
1106
|
+
while current_x < max_x
|
|
1107
|
+
screen.put(current_x, y, ' ')
|
|
1108
|
+
current_x += 1
|
|
1109
|
+
end
|
|
1110
|
+
end
|
|
1111
|
+
|
|
1007
1112
|
|
|
1008
1113
|
def get_display_entries
|
|
1009
1114
|
entries = if @keybind_handler.filter_active?
|
data/lib/rufio/version.rb
CHANGED
data/lib/rufio.rb
CHANGED
|
@@ -16,6 +16,8 @@ require_relative 'rufio/dialog_renderer'
|
|
|
16
16
|
require_relative 'rufio/text_utils'
|
|
17
17
|
require_relative 'rufio/logger'
|
|
18
18
|
require_relative 'rufio/keybind_handler'
|
|
19
|
+
require_relative 'rufio/ansi_line_parser'
|
|
20
|
+
require_relative 'rufio/syntax_highlighter'
|
|
19
21
|
require_relative 'rufio/file_preview'
|
|
20
22
|
require_relative 'rufio/terminal_ui'
|
|
21
23
|
require_relative 'rufio/application'
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rufio
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.80.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- masisz
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-02-
|
|
11
|
+
date: 2026-02-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: io-console
|
|
@@ -137,6 +137,7 @@ files:
|
|
|
137
137
|
- docs/CHANGELOG_v0.6.0.md
|
|
138
138
|
- docs/CHANGELOG_v0.7.0.md
|
|
139
139
|
- docs/CHANGELOG_v0.8.0.md
|
|
140
|
+
- docs/CHANGELOG_v0.80.0.md
|
|
140
141
|
- docs/CHANGELOG_v0.9.0.md
|
|
141
142
|
- examples/bookmarks.yml
|
|
142
143
|
- examples/config.rb
|
|
@@ -145,6 +146,7 @@ files:
|
|
|
145
146
|
- info/keybindings.md
|
|
146
147
|
- info/welcome.md
|
|
147
148
|
- lib/rufio.rb
|
|
149
|
+
- lib/rufio/ansi_line_parser.rb
|
|
148
150
|
- lib/rufio/application.rb
|
|
149
151
|
- lib/rufio/async_scanner_fiber.rb
|
|
150
152
|
- lib/rufio/async_scanner_promise.rb
|
|
@@ -191,6 +193,7 @@ files:
|
|
|
191
193
|
- lib/rufio/script_runner.rb
|
|
192
194
|
- lib/rufio/selection_manager.rb
|
|
193
195
|
- lib/rufio/shell_command_completion.rb
|
|
196
|
+
- lib/rufio/syntax_highlighter.rb
|
|
194
197
|
- lib/rufio/tab_mode_manager.rb
|
|
195
198
|
- lib/rufio/task_status.rb
|
|
196
199
|
- lib/rufio/terminal_ui.rb
|