sergeant 1.0.8 → 1.0.9
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 +8 -0
- data/Gemfile +3 -0
- data/README.md +1 -1
- data/lib/sergeant/rendering.rb +44 -1
- data/lib/sergeant/utils.rb +16 -0
- data/lib/sergeant/version.rb +1 -1
- data/lib/sergeant.rb +61 -6
- data/sergeant.gemspec +1 -0
- metadata +19 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0ac7149617ce3dd0a2e0c767ff46f6d2627fe917383303835f40a37461097dd8
|
|
4
|
+
data.tar.gz: 3d5621c41d6eec97194f62cab6ed525e1e1dd826fc9ed3fce3f9a08d67fba52a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1168b03a1a86233bebd92e7b287d10e33264e4d80b025bb06087b48a2560fa3f8f6c1c35e16634eedf64a7a60e723a01fb0755481ad7f32e9748a871a9be5639
|
|
7
|
+
data.tar.gz: 4111f86ea5fc31114d1e6a691bebf3e3a21beb4b1de6b4e26a1d34d11825c895ff3e3acdf568d5e1c818838402f06709e3bf715d12687725b1d9ea6530d4da9d
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
## [1.0.9] - 2026-08-19
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Syntax highlighting in the side preview panel** - text file previews are now colorized (keywords, strings, comments, numbers, names) using [Rouge](https://github.com/rouge-ruby/rouge), which guesses the language from the filename
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **Starship / Ruby-version-manager compatibility** - `Gemfile` no longer declares `ruby '>= 2.7.0'`, a version-range constraint that tools which auto-detect a project's Ruby version from the Gemfile (e.g. Starship's prompt) can't parse, causing an `Unknown ruby interpreter version` error just from `cd`-ing into the project. The constraint is already enforced by `required_ruby_version` in the gemspec.
|
|
16
|
+
|
|
9
17
|
## [1.0.8] - 2026-08-19
|
|
10
18
|
|
|
11
19
|
### Fixed
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Simple, fast, and elegant.
|
|
|
16
16
|
- 🗂️ **Visual Directory Navigation** - See all directories and files at a glance
|
|
17
17
|
- ⌨️ **Keyboard Driven** - Arrow keys, vim bindings (hjkl), and shortcuts
|
|
18
18
|
- 🎨 **Color-Coded Display** - Directories in cyan; files colored by type (code, archives, media, executables)
|
|
19
|
-
- 👀 **Live Preview Pane** - Text files and directory contents preview in a side panel as you move (press 'P' to toggle)
|
|
19
|
+
- 👀 **Live Preview Pane** - Text files and directory contents preview in a side panel as you move (press 'P' to toggle), with syntax highlighting powered by [Rouge](https://github.com/rouge-ruby/rouge)
|
|
20
20
|
- 📊 **Smart Scrolling** - Handles directories with hundreds of items
|
|
21
21
|
- 🔍 **Git Branch Display** - Shows current git branch in header
|
|
22
22
|
- 👤 **Ownership Toggle** - View file permissions and ownership (press 'o')
|
data/lib/sergeant/rendering.rb
CHANGED
|
@@ -26,6 +26,18 @@ module Sergeant
|
|
|
26
26
|
executable: 10
|
|
27
27
|
}.freeze
|
|
28
28
|
|
|
29
|
+
# Maps token_category (Sergeant::Utils) to the syntax-highlight color
|
|
30
|
+
# pairs set up in Sergeant#apply_color_theme (pairs 11-16).
|
|
31
|
+
PREVIEW_TOKEN_COLOR_PAIRS = {
|
|
32
|
+
keyword: 11,
|
|
33
|
+
string: 12,
|
|
34
|
+
comment: 13,
|
|
35
|
+
number: 14,
|
|
36
|
+
function: 15,
|
|
37
|
+
constant: 15,
|
|
38
|
+
error: 16
|
|
39
|
+
}.freeze
|
|
40
|
+
|
|
29
41
|
def draw_screen
|
|
30
42
|
# `erase` only rewrites the virtual buffer; `refresh` then diffs it
|
|
31
43
|
# against the physical screen and repaints just the changed cells.
|
|
@@ -222,7 +234,7 @@ module Sergeant
|
|
|
222
234
|
if @preview_lines.empty?
|
|
223
235
|
draw_preview_message(col, width, '(empty file)')
|
|
224
236
|
else
|
|
225
|
-
|
|
237
|
+
draw_preview_highlighted_lines(col, width, visible_lines, @preview_lines)
|
|
226
238
|
end
|
|
227
239
|
when :directory
|
|
228
240
|
if @preview_lines.empty?
|
|
@@ -274,6 +286,37 @@ module Sergeant
|
|
|
274
286
|
end
|
|
275
287
|
end
|
|
276
288
|
|
|
289
|
+
# Like draw_preview_lines, but each line is an array of
|
|
290
|
+
# {text:, category:} segments (built by Sergeant#build_text_preview via
|
|
291
|
+
# Rouge) instead of a single string, so every token can carry its own
|
|
292
|
+
# syntax-highlight color.
|
|
293
|
+
def draw_preview_highlighted_lines(col, width, visible_lines, lines)
|
|
294
|
+
visible_lines.times do |idx|
|
|
295
|
+
setpos(idx + 3, col)
|
|
296
|
+
draw_preview_segments(lines[idx] || [], width)
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def draw_preview_segments(segments, width)
|
|
301
|
+
remaining = width
|
|
302
|
+
|
|
303
|
+
segments.each do |segment|
|
|
304
|
+
break if remaining <= 0
|
|
305
|
+
|
|
306
|
+
text = segment[:text]
|
|
307
|
+
chunk = text.length > remaining ? text[0, remaining] : text
|
|
308
|
+
|
|
309
|
+
attron(preview_token_attr(segment[:category])) { addstr(chunk) }
|
|
310
|
+
remaining -= chunk.length
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
attron(color_pair(2)) { addstr(' ' * remaining) } if remaining.positive?
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def preview_token_attr(category)
|
|
317
|
+
color_pair(PREVIEW_TOKEN_COLOR_PAIRS[category] || 2)
|
|
318
|
+
end
|
|
319
|
+
|
|
277
320
|
def draw_preview_message(col, width, message)
|
|
278
321
|
setpos(3, col)
|
|
279
322
|
attron(color_pair(2) | Curses::A_DIM) do
|
data/lib/sergeant/utils.rb
CHANGED
|
@@ -26,6 +26,22 @@ module Sergeant
|
|
|
26
26
|
:file
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
# Maps a Rouge token's qualified name (e.g. "Literal.String.Double") to
|
|
30
|
+
# one of the syntax-highlight color pairs set up in
|
|
31
|
+
# Sergeant#apply_color_theme. Unmatched token types (Text, Punctuation,
|
|
32
|
+
# Operator, ...) return nil, meaning "draw in the default text color".
|
|
33
|
+
def token_category(qualname)
|
|
34
|
+
case qualname
|
|
35
|
+
when /\AComment/ then :comment
|
|
36
|
+
when /\AKeyword/ then :keyword
|
|
37
|
+
when /\ALiteral\.String/ then :string
|
|
38
|
+
when /\ALiteral\.Number/ then :number
|
|
39
|
+
when /\AName\.(Function|Class|Namespace|Tag)/ then :function
|
|
40
|
+
when /\AName\.(Constant|Builtin|Variable\.Class)/ then :constant
|
|
41
|
+
when /\A(Error|Generic\.Error|Generic\.Traceback)/ then :error
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
29
45
|
def get_git_branch
|
|
30
46
|
return nil unless Dir.exist?(File.join(@current_dir, '.git'))
|
|
31
47
|
|
data/lib/sergeant/version.rb
CHANGED
data/lib/sergeant.rb
CHANGED
|
@@ -7,6 +7,7 @@ require 'curses'
|
|
|
7
7
|
require 'pathname'
|
|
8
8
|
require 'etc'
|
|
9
9
|
require 'fileutils'
|
|
10
|
+
require 'rouge'
|
|
10
11
|
|
|
11
12
|
require_relative 'sergeant/version'
|
|
12
13
|
require_relative 'sergeant/config'
|
|
@@ -184,6 +185,15 @@ class SergeantApp
|
|
|
184
185
|
init_pair(8, Sergeant::Config.get_color(@config['media']), Curses::COLOR_BLACK)
|
|
185
186
|
init_pair(9, Sergeant::Config.get_color(@config['code']), Curses::COLOR_BLACK)
|
|
186
187
|
init_pair(10, Sergeant::Config.get_color(@config['executables']), Curses::COLOR_BLACK)
|
|
188
|
+
|
|
189
|
+
# Syntax highlighting for the preview panel - not user-configurable
|
|
190
|
+
# (yet), curses only gives us the 8 base colors to work with anyway.
|
|
191
|
+
init_pair(11, Curses::COLOR_MAGENTA, Curses::COLOR_BLACK) # keyword
|
|
192
|
+
init_pair(12, Curses::COLOR_GREEN, Curses::COLOR_BLACK) # string
|
|
193
|
+
init_pair(13, Curses::COLOR_WHITE, Curses::COLOR_BLACK) # comment (dimmed)
|
|
194
|
+
init_pair(14, Curses::COLOR_CYAN, Curses::COLOR_BLACK) # number
|
|
195
|
+
init_pair(15, Curses::COLOR_YELLOW, Curses::COLOR_BLACK) # function/class name
|
|
196
|
+
init_pair(16, Curses::COLOR_RED, Curses::COLOR_BLACK) # error token
|
|
187
197
|
end
|
|
188
198
|
|
|
189
199
|
def search_files
|
|
@@ -384,7 +394,7 @@ class SergeantApp
|
|
|
384
394
|
if item[:type] == :directory
|
|
385
395
|
[:directory, build_directory_preview(item[:path])]
|
|
386
396
|
elsif text_file?(item[:path])
|
|
387
|
-
[:text, build_text_preview(item[:path])]
|
|
397
|
+
[:text, build_text_preview(item[:path], item[:name])]
|
|
388
398
|
else
|
|
389
399
|
[:binary, []]
|
|
390
400
|
end
|
|
@@ -405,20 +415,65 @@ class SergeantApp
|
|
|
405
415
|
[]
|
|
406
416
|
end
|
|
407
417
|
|
|
408
|
-
def build_text_preview(path)
|
|
409
|
-
|
|
418
|
+
def build_text_preview(path, filename)
|
|
419
|
+
raw_lines = []
|
|
410
420
|
|
|
411
421
|
File.foreach(path, mode: 'rb') do |line|
|
|
412
422
|
text = line.byteslice(0, PREVIEW_LINE_MAX_BYTES).to_s.chomp
|
|
413
|
-
|
|
414
|
-
break if
|
|
423
|
+
raw_lines << text.force_encoding('UTF-8').scrub('?').gsub("\t", ' ')
|
|
424
|
+
break if raw_lines.length >= PREVIEW_LINE_LIMIT
|
|
415
425
|
end
|
|
416
426
|
|
|
417
|
-
|
|
427
|
+
highlight_lines(raw_lines, filename)
|
|
418
428
|
rescue StandardError
|
|
419
429
|
[]
|
|
420
430
|
end
|
|
421
431
|
|
|
432
|
+
# Runs the preview text through Rouge and reassembles it into one array
|
|
433
|
+
# per line of [{text:, category:}, ...] segments, so the preview panel can
|
|
434
|
+
# paint each token in its own color. Falls back to plain (uncategorized)
|
|
435
|
+
# lines if Rouge can't make sense of the file - a highlighting bug should
|
|
436
|
+
# never be able to break the preview itself.
|
|
437
|
+
def highlight_lines(raw_lines, filename)
|
|
438
|
+
return [] if raw_lines.empty?
|
|
439
|
+
|
|
440
|
+
text = raw_lines.join("\n")
|
|
441
|
+
lexer = guess_lexer(filename, text)
|
|
442
|
+
tokens_to_lines(lexer, text)
|
|
443
|
+
rescue StandardError
|
|
444
|
+
raw_lines.map { |line| [{ text: line, category: nil }] }
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def guess_lexer(filename, text)
|
|
448
|
+
Rouge::Lexer.guess(filename: filename, source: text)
|
|
449
|
+
rescue Rouge::Guesser::Ambiguous => e
|
|
450
|
+
e.alternatives.first || Rouge::Lexers::PlainText
|
|
451
|
+
rescue StandardError
|
|
452
|
+
Rouge::Lexers::PlainText
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
def tokens_to_lines(lexer, text)
|
|
456
|
+
lines = []
|
|
457
|
+
current = []
|
|
458
|
+
|
|
459
|
+
lexer.lex(text) do |token, value|
|
|
460
|
+
category = token_category(token.qualname)
|
|
461
|
+
pieces = value.split("\n", -1)
|
|
462
|
+
|
|
463
|
+
pieces.each_with_index do |piece, idx|
|
|
464
|
+
current << { text: piece, category: category } unless piece.empty?
|
|
465
|
+
|
|
466
|
+
next if idx == pieces.length - 1
|
|
467
|
+
|
|
468
|
+
lines << current
|
|
469
|
+
current = []
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
lines << current unless current.empty?
|
|
474
|
+
lines
|
|
475
|
+
end
|
|
476
|
+
|
|
422
477
|
def refresh_items
|
|
423
478
|
entries = Dir.entries(@current_dir).reject { |e| e == '.' || e == '..' }
|
|
424
479
|
@items = []
|
data/sergeant.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sergeant
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mateusz Grotha
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-08-24 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: curses
|
|
@@ -23,6 +24,20 @@ dependencies:
|
|
|
23
24
|
- - "~>"
|
|
24
25
|
- !ruby/object:Gem::Version
|
|
25
26
|
version: '1.4'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rouge
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '4.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '4.0'
|
|
26
41
|
- !ruby/object:Gem::Dependency
|
|
27
42
|
name: rspec
|
|
28
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -106,7 +121,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
106
121
|
- !ruby/object:Gem::Version
|
|
107
122
|
version: '0'
|
|
108
123
|
requirements: []
|
|
109
|
-
rubygems_version:
|
|
124
|
+
rubygems_version: 3.5.22
|
|
125
|
+
signing_key:
|
|
110
126
|
specification_version: 4
|
|
111
127
|
summary: Interactive TUI Directory Navigator for Terminal
|
|
112
128
|
test_files: []
|