sight 0.1.0 → 0.2.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 +8 -0
- data/README.md +5 -11
- data/lib/sight/app.rb +46 -29
- data/lib/sight/cli.rb +2 -2
- data/lib/sight/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 24b700f118e81b323388d9dc1413bcb493a8c8625664099fcff2eeb524fbe4e7
|
|
4
|
+
data.tar.gz: c0b1e78505b39f82909607431c5c7cf27a56545292abb0967887f2a60be04420
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b3e9f75deef46a4da613023322b833f0bbf86c7f2cc5f3ccde44f30fd7bd0cb1712d4925c5ed18a865e4ff6fe488d787eaba38939c52cea2e0a9d1f62fec4a97
|
|
7
|
+
data.tar.gz: 6530759983f83fed70bab30adb12fa6bae9990f5554e91170e5547cbeb58514759eb649fe322b1857d895ec0051017ab53ee2b9cbc35bb918b66199c9fbfbf51
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.0] - 2026-03-04
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Navigation is now hunk-based: `j`/`k` jump between hunks
|
|
8
|
+
- Active hunk is highlighted; inactive hunks are dimmed
|
|
9
|
+
- Removed line-scrolling keys (`f`/`b`/`d`/`u`/`g`/`G`) and arrow keys
|
|
10
|
+
|
|
3
11
|
## [0.1.0] - 2026-03-04
|
|
4
12
|
|
|
5
13
|
### Added
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Sight
|
|
2
2
|
|
|
3
|
-
Terminal UI for browsing git diffs interactively with colors,
|
|
3
|
+
Terminal UI for browsing git diffs interactively with colors, hunk navigation, and file navigation.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -20,16 +20,10 @@ sight
|
|
|
20
20
|
|
|
21
21
|
| Key | Action |
|
|
22
22
|
|-----|--------|
|
|
23
|
-
| `j`
|
|
24
|
-
| `k`
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `f` | Full page down |
|
|
28
|
-
| `b` | Full page up |
|
|
29
|
-
| `g` | Go to top |
|
|
30
|
-
| `G` | Go to bottom |
|
|
31
|
-
| `n` / `→` | Next file |
|
|
32
|
-
| `p` / `←` | Previous file |
|
|
23
|
+
| `j` | Next hunk |
|
|
24
|
+
| `k` | Previous hunk |
|
|
25
|
+
| `n` | Next file |
|
|
26
|
+
| `p` | Previous file |
|
|
33
27
|
| `?` | Toggle help |
|
|
34
28
|
| `q` / `Esc` | Quit |
|
|
35
29
|
|
data/lib/sight/app.rb
CHANGED
|
@@ -5,13 +5,15 @@ require "curses"
|
|
|
5
5
|
module Sight
|
|
6
6
|
class App
|
|
7
7
|
attr_reader :files, :file_lines
|
|
8
|
-
attr_accessor :file_idx, :offset
|
|
8
|
+
attr_accessor :file_idx, :offset, :hunk_idx
|
|
9
9
|
|
|
10
10
|
def initialize(files)
|
|
11
11
|
@files = files
|
|
12
12
|
@file_lines = files.map { build_file_lines(it) }
|
|
13
13
|
@file_idx = 0
|
|
14
14
|
@offset = 0
|
|
15
|
+
@hunk_idx = 0
|
|
16
|
+
@hunk_offsets_cache = {}
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
def run
|
|
@@ -49,6 +51,7 @@ module Sight
|
|
|
49
51
|
Curses.init_pair(2, Curses::COLOR_RED, -1)
|
|
50
52
|
Curses.init_pair(3, Curses::COLOR_CYAN, -1)
|
|
51
53
|
Curses.init_pair(4, Curses::COLOR_YELLOW, -1)
|
|
54
|
+
Curses.init_pair(5, 240, -1)
|
|
52
55
|
end
|
|
53
56
|
|
|
54
57
|
def lines
|
|
@@ -78,20 +81,29 @@ module Sight
|
|
|
78
81
|
dim = Curses.color_pair(0) | Curses::A_DIM
|
|
79
82
|
content_width = width - gutter - 3
|
|
80
83
|
|
|
84
|
+
selected_start = hunk_offsets[hunk_idx]
|
|
85
|
+
selected_end = if hunk_idx + 1 < hunk_offsets.size
|
|
86
|
+
hunk_offsets[hunk_idx + 1]
|
|
87
|
+
else
|
|
88
|
+
lines.size
|
|
89
|
+
end
|
|
90
|
+
|
|
81
91
|
scroll_height.times do |row|
|
|
82
92
|
idx = offset + row
|
|
83
93
|
break if idx >= lines.size
|
|
84
94
|
line = lines[idx]
|
|
85
95
|
win.setpos(row + 2, 0)
|
|
86
|
-
|
|
87
|
-
win.attron(
|
|
96
|
+
active = idx >= selected_start && idx < selected_end
|
|
97
|
+
win.attron(dim) { win.addstr("#{format_gutter(line.type, line.lineno, gutter)} \u2502 ") }
|
|
98
|
+
attr = active ? color_for(line.type) : Curses.color_pair(5)
|
|
99
|
+
win.attron(attr) { win.addstr(line.text[0, content_width]) }
|
|
88
100
|
end
|
|
89
101
|
end
|
|
90
102
|
|
|
91
103
|
def render_status_bar(win, width)
|
|
92
104
|
win.setpos(Curses.lines - 1, 0)
|
|
93
105
|
win.attron(Curses.color_pair(4) | Curses::A_REVERSE) do
|
|
94
|
-
status = " File #{file_idx + 1}/#{files.size} | Line #{offset + 1}/#{lines.size} "
|
|
106
|
+
status = " File #{file_idx + 1}/#{files.size} | Hunk #{hunk_idx + 1}/#{hunk_offsets.size} | Line #{offset + 1}/#{lines.size} "
|
|
95
107
|
win.addstr(status.ljust(width))
|
|
96
108
|
end
|
|
97
109
|
end
|
|
@@ -126,41 +138,24 @@ module Sight
|
|
|
126
138
|
end
|
|
127
139
|
end
|
|
128
140
|
|
|
129
|
-
def scroll_to(delta)
|
|
130
|
-
max = [0, lines.size - scroll_height].max
|
|
131
|
-
self.offset = (offset + delta).clamp(0, max)
|
|
132
|
-
end
|
|
133
|
-
|
|
134
141
|
def handle_input
|
|
135
142
|
key = Curses.getch
|
|
136
143
|
case key
|
|
137
144
|
when "q", 27 then return false
|
|
138
|
-
when "j"
|
|
139
|
-
when "k"
|
|
140
|
-
when "
|
|
141
|
-
when "
|
|
142
|
-
when "d" then scroll_to(scroll_height / 2)
|
|
143
|
-
when "u" then scroll_to(-scroll_height / 2)
|
|
144
|
-
when "g" then self.offset = 0
|
|
145
|
-
when "G" then scroll_to(lines.size)
|
|
146
|
-
when "n", Curses::KEY_RIGHT then jump_file(1)
|
|
147
|
-
when "p", Curses::KEY_LEFT then jump_file(-1)
|
|
145
|
+
when "j" then jump_hunk(1)
|
|
146
|
+
when "k" then jump_hunk(-1)
|
|
147
|
+
when "n" then jump_file(1)
|
|
148
|
+
when "p" then jump_file(-1)
|
|
148
149
|
when "?" then show_help
|
|
149
150
|
end
|
|
150
151
|
true
|
|
151
152
|
end
|
|
152
153
|
|
|
153
154
|
HELP_KEYS = [
|
|
154
|
-
["j
|
|
155
|
-
["k
|
|
156
|
-
["
|
|
157
|
-
["
|
|
158
|
-
["f", "Full page down"],
|
|
159
|
-
["b", "Full page up"],
|
|
160
|
-
["g", "Go to top"],
|
|
161
|
-
["G", "Go to bottom"],
|
|
162
|
-
["n / →", "Next file"],
|
|
163
|
-
["p / ←", "Previous file"],
|
|
155
|
+
["j", "Next hunk"],
|
|
156
|
+
["k", "Previous hunk"],
|
|
157
|
+
["n", "Next file"],
|
|
158
|
+
["p", "Previous file"],
|
|
164
159
|
["q / Esc", "Quit"],
|
|
165
160
|
["?", "Toggle this help"]
|
|
166
161
|
].freeze
|
|
@@ -201,11 +196,33 @@ module Sight
|
|
|
201
196
|
end
|
|
202
197
|
end
|
|
203
198
|
|
|
199
|
+
def hunk_offsets
|
|
200
|
+
@hunk_offsets_cache[file_idx] ||= begin
|
|
201
|
+
offsets = []
|
|
202
|
+
line_idx = 0
|
|
203
|
+
files[file_idx].hunks.each do |hunk|
|
|
204
|
+
offsets << line_idx
|
|
205
|
+
line_idx += hunk.lines.size
|
|
206
|
+
end
|
|
207
|
+
offsets
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def jump_hunk(delta)
|
|
212
|
+
return if hunk_offsets.empty?
|
|
213
|
+
self.hunk_idx = (hunk_idx + delta).clamp(0, hunk_offsets.size - 1)
|
|
214
|
+
target = hunk_offsets[hunk_idx]
|
|
215
|
+
margin = [2, scroll_height / 4].min
|
|
216
|
+
max = [0, lines.size - scroll_height].max
|
|
217
|
+
self.offset = [target - margin, 0].max.clamp(0, max)
|
|
218
|
+
end
|
|
219
|
+
|
|
204
220
|
def jump_file(direction)
|
|
205
221
|
new_idx = (file_idx + direction).clamp(0, files.size - 1)
|
|
206
222
|
return if new_idx == file_idx
|
|
207
223
|
self.file_idx = new_idx
|
|
208
224
|
self.offset = 0
|
|
225
|
+
self.hunk_idx = 0
|
|
209
226
|
end
|
|
210
227
|
end
|
|
211
228
|
end
|
data/lib/sight/cli.rb
CHANGED
|
@@ -9,7 +9,7 @@ module Sight
|
|
|
9
9
|
puts "Usage: sight"
|
|
10
10
|
puts "Interactive git diff viewer (staged + unstaged)"
|
|
11
11
|
puts
|
|
12
|
-
puts "Keys: j/k
|
|
12
|
+
puts "Keys: j/k hunks, n/p files, ? help, q quit"
|
|
13
13
|
return
|
|
14
14
|
end
|
|
15
15
|
|
|
@@ -20,7 +20,7 @@ module Sight
|
|
|
20
20
|
|
|
21
21
|
raw = Git.diff
|
|
22
22
|
if raw.empty?
|
|
23
|
-
warn "No diff output
|
|
23
|
+
warn "No diff output"
|
|
24
24
|
return
|
|
25
25
|
end
|
|
26
26
|
|
data/lib/sight/version.rb
CHANGED