textbringer 19 → 24
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/CLAUDE.md +0 -6
- data/lib/textbringer/buffer.rb +2 -0
- data/lib/textbringer/commands/dired.rb +31 -0
- data/lib/textbringer/commands/files.rb +4 -0
- data/lib/textbringer/commands/gamegrid.rb +25 -0
- data/lib/textbringer/commands/tetris.rb +10 -0
- data/lib/textbringer/face.rb +6 -6
- data/lib/textbringer/faces/dired.rb +6 -0
- data/lib/textbringer/faces/gamegrid.rb +23 -0
- data/lib/textbringer/floating_window.rb +15 -40
- data/lib/textbringer/gamegrid.rb +164 -0
- data/lib/textbringer/input_method.rb +6 -0
- data/lib/textbringer/input_methods/skk_input_method.rb +132 -22
- data/lib/textbringer/keymap.rb +1 -0
- data/lib/textbringer/modes/dired_mode.rb +322 -0
- data/lib/textbringer/modes/gamegrid_mode.rb +46 -0
- data/lib/textbringer/modes/tetris_mode.rb +316 -0
- data/lib/textbringer/modes/transient_mark_mode.rb +2 -2
- data/lib/textbringer/version.rb +1 -1
- data/lib/textbringer/window.rb +71 -88
- data/lib/textbringer.rb +7 -0
- metadata +11 -2
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
module Textbringer
|
|
2
|
+
class TetrisMode < GamegridMode
|
|
3
|
+
BOARD_WIDTH = 10
|
|
4
|
+
BOARD_HEIGHT = 20
|
|
5
|
+
BORDER_VALUE = 8 # cell value used for the 1-cell border around the board
|
|
6
|
+
|
|
7
|
+
PIECE_COLORS = {
|
|
8
|
+
1 => :gamegrid_block_cyan,
|
|
9
|
+
2 => :gamegrid_block_yellow,
|
|
10
|
+
3 => :gamegrid_block_magenta,
|
|
11
|
+
4 => :gamegrid_block_green,
|
|
12
|
+
5 => :gamegrid_block_red,
|
|
13
|
+
6 => :gamegrid_block_blue,
|
|
14
|
+
7 => :gamegrid_block_white,
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
PIECE_NAMES = ["", "I", "O", "T", "S", "Z", "J", "L"].freeze
|
|
18
|
+
|
|
19
|
+
# Pieces[type][rotation][row][col] — 4×4 bounding box, 1-indexed types
|
|
20
|
+
PIECES = [
|
|
21
|
+
nil,
|
|
22
|
+
# 1: I (cyan)
|
|
23
|
+
[
|
|
24
|
+
[[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]],
|
|
25
|
+
[[0,0,1,0],[0,0,1,0],[0,0,1,0],[0,0,1,0]],
|
|
26
|
+
[[0,0,0,0],[0,0,0,0],[1,1,1,1],[0,0,0,0]],
|
|
27
|
+
[[0,1,0,0],[0,1,0,0],[0,1,0,0],[0,1,0,0]],
|
|
28
|
+
],
|
|
29
|
+
# 2: O (yellow)
|
|
30
|
+
[
|
|
31
|
+
[[0,1,1,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]],
|
|
32
|
+
[[0,1,1,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]],
|
|
33
|
+
[[0,1,1,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]],
|
|
34
|
+
[[0,1,1,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]],
|
|
35
|
+
],
|
|
36
|
+
# 3: T (magenta)
|
|
37
|
+
[
|
|
38
|
+
[[0,1,0,0],[1,1,1,0],[0,0,0,0],[0,0,0,0]],
|
|
39
|
+
[[0,1,0,0],[0,1,1,0],[0,1,0,0],[0,0,0,0]],
|
|
40
|
+
[[0,0,0,0],[1,1,1,0],[0,1,0,0],[0,0,0,0]],
|
|
41
|
+
[[0,1,0,0],[1,1,0,0],[0,1,0,0],[0,0,0,0]],
|
|
42
|
+
],
|
|
43
|
+
# 4: S (green)
|
|
44
|
+
[
|
|
45
|
+
[[0,1,1,0],[1,1,0,0],[0,0,0,0],[0,0,0,0]],
|
|
46
|
+
[[1,0,0,0],[1,1,0,0],[0,1,0,0],[0,0,0,0]],
|
|
47
|
+
[[0,1,1,0],[1,1,0,0],[0,0,0,0],[0,0,0,0]],
|
|
48
|
+
[[1,0,0,0],[1,1,0,0],[0,1,0,0],[0,0,0,0]],
|
|
49
|
+
],
|
|
50
|
+
# 5: Z (red)
|
|
51
|
+
[
|
|
52
|
+
[[1,1,0,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]],
|
|
53
|
+
[[0,0,1,0],[0,1,1,0],[0,1,0,0],[0,0,0,0]],
|
|
54
|
+
[[1,1,0,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]],
|
|
55
|
+
[[0,0,1,0],[0,1,1,0],[0,1,0,0],[0,0,0,0]],
|
|
56
|
+
],
|
|
57
|
+
# 6: J (blue)
|
|
58
|
+
[
|
|
59
|
+
[[1,0,0,0],[1,1,1,0],[0,0,0,0],[0,0,0,0]],
|
|
60
|
+
[[0,1,1,0],[0,1,0,0],[0,1,0,0],[0,0,0,0]],
|
|
61
|
+
[[0,0,0,0],[1,1,1,0],[0,0,1,0],[0,0,0,0]],
|
|
62
|
+
[[0,1,0,0],[0,1,0,0],[1,1,0,0],[0,0,0,0]],
|
|
63
|
+
],
|
|
64
|
+
# 7: L (white)
|
|
65
|
+
[
|
|
66
|
+
[[0,0,1,0],[1,1,1,0],[0,0,0,0],[0,0,0,0]],
|
|
67
|
+
[[0,1,0,0],[0,1,0,0],[0,1,1,0],[0,0,0,0]],
|
|
68
|
+
[[0,0,0,0],[1,1,1,0],[1,0,0,0],[0,0,0,0]],
|
|
69
|
+
[[1,1,0,0],[0,1,0,0],[0,1,0,0],[0,0,0,0]],
|
|
70
|
+
],
|
|
71
|
+
].freeze
|
|
72
|
+
|
|
73
|
+
define_keymap :TETRIS_MODE_MAP
|
|
74
|
+
TETRIS_MODE_MAP.define_key("q", :gamegrid_quit_command)
|
|
75
|
+
TETRIS_MODE_MAP.define_key("n", :tetris_new_game_command)
|
|
76
|
+
TETRIS_MODE_MAP.define_key(:left, :tetris_move_left_command)
|
|
77
|
+
TETRIS_MODE_MAP.define_key("h", :tetris_move_left_command)
|
|
78
|
+
TETRIS_MODE_MAP.define_key(:right, :tetris_move_right_command)
|
|
79
|
+
TETRIS_MODE_MAP.define_key("l", :tetris_move_right_command)
|
|
80
|
+
TETRIS_MODE_MAP.define_key(:down, :tetris_move_down_command)
|
|
81
|
+
TETRIS_MODE_MAP.define_key("j", :tetris_move_down_command)
|
|
82
|
+
TETRIS_MODE_MAP.define_key(:up, :tetris_rotate_command)
|
|
83
|
+
TETRIS_MODE_MAP.define_key("k", :tetris_rotate_command)
|
|
84
|
+
TETRIS_MODE_MAP.define_key(" ", :tetris_drop_command)
|
|
85
|
+
TETRIS_MODE_MAP.define_key("p", :tetris_pause_command)
|
|
86
|
+
|
|
87
|
+
attr_reader :score, :level, :lines_cleared,
|
|
88
|
+
:piece_type, :piece_rot, :piece_x, :piece_y,
|
|
89
|
+
:next_type, :game_over, :paused
|
|
90
|
+
|
|
91
|
+
def initialize(buffer)
|
|
92
|
+
super
|
|
93
|
+
buffer.keymap = TETRIS_MODE_MAP
|
|
94
|
+
@game_over = true
|
|
95
|
+
@paused = false
|
|
96
|
+
@grid = nil
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
define_local_command(:tetris_new_game, doc: "Start a new Tetris game.") do
|
|
100
|
+
@grid&.stop_timer
|
|
101
|
+
@grid = gamegrid_init(BOARD_WIDTH + 2, BOARD_HEIGHT + 2, margin_left: 2)
|
|
102
|
+
@grid.set_display_option(0, char: " ")
|
|
103
|
+
@grid.set_display_option(BORDER_VALUE, char: "[]", face: :gamegrid_border)
|
|
104
|
+
PIECE_COLORS.each { |v, f| @grid.set_display_option(v, char: "[]", face: f) }
|
|
105
|
+
|
|
106
|
+
@board = Array.new(BOARD_HEIGHT) { Array.new(BOARD_WIDTH, 0) }
|
|
107
|
+
@score = 0
|
|
108
|
+
@level = 1
|
|
109
|
+
@lines_cleared = 0
|
|
110
|
+
@game_over = false
|
|
111
|
+
@paused = false
|
|
112
|
+
@next_type = random_piece_type
|
|
113
|
+
|
|
114
|
+
spawn_piece
|
|
115
|
+
start_game_timer unless @game_over
|
|
116
|
+
render_board
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
define_local_command(:tetris_move_left, doc: "Move piece left.") do
|
|
120
|
+
return unless active?
|
|
121
|
+
if valid_position?(@piece_x - 1, @piece_y, @piece_type, @piece_rot)
|
|
122
|
+
@piece_x -= 1
|
|
123
|
+
render_board
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
define_local_command(:tetris_move_right, doc: "Move piece right.") do
|
|
128
|
+
return unless active?
|
|
129
|
+
if valid_position?(@piece_x + 1, @piece_y, @piece_type, @piece_rot)
|
|
130
|
+
@piece_x += 1
|
|
131
|
+
render_board
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
define_local_command(:tetris_move_down, doc: "Soft-drop current piece.") do
|
|
136
|
+
return unless active?
|
|
137
|
+
step_down
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
define_local_command(:tetris_rotate, doc: "Rotate current piece clockwise.") do
|
|
141
|
+
return unless active?
|
|
142
|
+
new_rot = (@piece_rot + 1) % 4
|
|
143
|
+
# Try basic rotation then simple wall-kicks (±1, ±2 columns)
|
|
144
|
+
[0, -1, 1, -2, 2].each do |kick|
|
|
145
|
+
if valid_position?(@piece_x + kick, @piece_y, @piece_type, new_rot)
|
|
146
|
+
@piece_x += kick
|
|
147
|
+
@piece_rot = new_rot
|
|
148
|
+
break
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
render_board
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
define_local_command(:tetris_drop, doc: "Hard-drop current piece.") do
|
|
155
|
+
return unless active?
|
|
156
|
+
while valid_position?(@piece_x, @piece_y + 1, @piece_type, @piece_rot)
|
|
157
|
+
@piece_y += 1
|
|
158
|
+
@score += 2
|
|
159
|
+
end
|
|
160
|
+
lock_and_continue
|
|
161
|
+
render_board
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
define_local_command(:tetris_pause, doc: "Toggle game pause.") do
|
|
165
|
+
return if @game_over || !@grid
|
|
166
|
+
@paused = !@paused
|
|
167
|
+
if @paused
|
|
168
|
+
@grid.stop_timer
|
|
169
|
+
else
|
|
170
|
+
start_game_timer
|
|
171
|
+
end
|
|
172
|
+
render_board
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
private
|
|
176
|
+
|
|
177
|
+
def active?
|
|
178
|
+
!@game_over && !@paused && @grid
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def random_piece_type
|
|
182
|
+
rand(1..7)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def spawn_piece
|
|
186
|
+
@piece_type = @next_type
|
|
187
|
+
@next_type = random_piece_type
|
|
188
|
+
@piece_rot = 0
|
|
189
|
+
@piece_x = BOARD_WIDTH / 2 - 2
|
|
190
|
+
@piece_y = 0
|
|
191
|
+
@game_over = !valid_position?(@piece_x, @piece_y, @piece_type, @piece_rot)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def valid_position?(x, y, type, rot)
|
|
195
|
+
PIECES[type][rot].each_with_index do |row, row_i|
|
|
196
|
+
row.each_with_index do |cell, col_i|
|
|
197
|
+
next if cell == 0
|
|
198
|
+
bx = x + col_i
|
|
199
|
+
by = y + row_i
|
|
200
|
+
return false if bx < 0 || bx >= BOARD_WIDTH
|
|
201
|
+
return false if by >= BOARD_HEIGHT
|
|
202
|
+
next if by < 0 # piece can start partially above the board
|
|
203
|
+
return false if @board[by][bx] != 0
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
true
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def lock_piece
|
|
210
|
+
PIECES[@piece_type][@piece_rot].each_with_index do |row, row_i|
|
|
211
|
+
row.each_with_index do |cell, col_i|
|
|
212
|
+
next if cell == 0
|
|
213
|
+
by = @piece_y + row_i
|
|
214
|
+
bx = @piece_x + col_i
|
|
215
|
+
next if by < 0 || by >= BOARD_HEIGHT || bx < 0 || bx >= BOARD_WIDTH
|
|
216
|
+
@board[by][bx] = @piece_type
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def clear_lines
|
|
222
|
+
full = (0...BOARD_HEIGHT).select { |y| @board[y].all? { |c| c != 0 } }
|
|
223
|
+
return 0 if full.empty?
|
|
224
|
+
full.reverse_each { |y| @board.delete_at(y) }
|
|
225
|
+
full.size.times { @board.unshift(Array.new(BOARD_WIDTH, 0)) }
|
|
226
|
+
n = full.size
|
|
227
|
+
@score += [0, 100, 300, 500, 800][n].to_i * @level
|
|
228
|
+
@lines_cleared += n
|
|
229
|
+
@level = @lines_cleared / 10 + 1
|
|
230
|
+
n
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def step_down
|
|
234
|
+
if valid_position?(@piece_x, @piece_y + 1, @piece_type, @piece_rot)
|
|
235
|
+
@piece_y += 1
|
|
236
|
+
else
|
|
237
|
+
lock_and_continue
|
|
238
|
+
end
|
|
239
|
+
render_board
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def lock_and_continue
|
|
243
|
+
lock_piece
|
|
244
|
+
clear_lines
|
|
245
|
+
spawn_piece
|
|
246
|
+
if @game_over
|
|
247
|
+
@grid.stop_timer
|
|
248
|
+
else
|
|
249
|
+
start_game_timer
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def start_game_timer
|
|
254
|
+
@grid.start_timer(timer_interval) { step_down }
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def timer_interval
|
|
258
|
+
# Level 1 = 1.0 s, each level adds 0.1 s speed, floor at 0.1 s
|
|
259
|
+
[1.0 - (@level - 1) * 0.1, 0.1].max
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def update_grid
|
|
263
|
+
# Border: top and bottom rows
|
|
264
|
+
(BOARD_WIDTH + 2).times do |x|
|
|
265
|
+
@grid.set_cell(x, 0, BORDER_VALUE)
|
|
266
|
+
@grid.set_cell(x, BOARD_HEIGHT + 1, BORDER_VALUE)
|
|
267
|
+
end
|
|
268
|
+
# Border: left and right columns (inner rows only)
|
|
269
|
+
BOARD_HEIGHT.times do |y|
|
|
270
|
+
@grid.set_cell(0, y + 1, BORDER_VALUE)
|
|
271
|
+
@grid.set_cell(BOARD_WIDTH + 1, y + 1, BORDER_VALUE)
|
|
272
|
+
end
|
|
273
|
+
# Board content, offset by (1, 1)
|
|
274
|
+
BOARD_HEIGHT.times do |y|
|
|
275
|
+
BOARD_WIDTH.times do |x|
|
|
276
|
+
@grid.set_cell(x + 1, y + 1, @board[y][x])
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
return if @game_over
|
|
280
|
+
# Current piece, offset by (1, 1)
|
|
281
|
+
PIECES[@piece_type][@piece_rot].each_with_index do |row, row_i|
|
|
282
|
+
row.each_with_index do |cell, col_i|
|
|
283
|
+
next if cell == 0
|
|
284
|
+
bx = @piece_x + col_i + 1
|
|
285
|
+
by = @piece_y + row_i + 1
|
|
286
|
+
next if bx < 1 || bx > BOARD_WIDTH || by < 1 || by > BOARD_HEIGHT
|
|
287
|
+
@grid.set_cell(bx, by, @piece_type)
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def render_board
|
|
293
|
+
update_grid
|
|
294
|
+
@buffer.read_only_edit do
|
|
295
|
+
@buffer.clear
|
|
296
|
+
@buffer.insert(@grid.render)
|
|
297
|
+
@buffer.insert("\n")
|
|
298
|
+
@buffer.insert(status_text)
|
|
299
|
+
@buffer.beginning_of_buffer
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def status_text
|
|
304
|
+
if @game_over
|
|
305
|
+
"GAME OVER Score: #{@score} Level: #{@level} Lines: #{@lines_cleared}" \
|
|
306
|
+
" [n]ew game [q]uit\n"
|
|
307
|
+
elsif @paused
|
|
308
|
+
"PAUSED Score: #{@score} Level: #{@level} Lines: #{@lines_cleared}" \
|
|
309
|
+
" [p] resume\n"
|
|
310
|
+
else
|
|
311
|
+
"Score: #{@score} Level: #{@level} Lines: #{@lines_cleared}" \
|
|
312
|
+
" Next: #{PIECE_NAMES[@next_type]}\n"
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
data/lib/textbringer/version.rb
CHANGED
data/lib/textbringer/window.rb
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
require "curses"
|
|
2
2
|
require_relative "window/fallback_characters"
|
|
3
3
|
|
|
4
|
+
unless Curses::Window.method_defined?(:attr_set)
|
|
5
|
+
using Module.new {
|
|
6
|
+
refine Curses::Window do
|
|
7
|
+
def attr_set(attrs, pair)
|
|
8
|
+
attrset(attrs | Curses.color_pair(pair))
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
}
|
|
12
|
+
end
|
|
13
|
+
|
|
4
14
|
module Textbringer
|
|
5
15
|
class Window
|
|
6
16
|
Cursor = Struct.new(:y, :x)
|
|
@@ -131,6 +141,8 @@ module Textbringer
|
|
|
131
141
|
require_relative "faces/basic"
|
|
132
142
|
require_relative "faces/programming"
|
|
133
143
|
require_relative "faces/completion"
|
|
144
|
+
require_relative "faces/dired"
|
|
145
|
+
require_relative "faces/gamegrid"
|
|
134
146
|
end
|
|
135
147
|
|
|
136
148
|
def self.start
|
|
@@ -146,6 +158,10 @@ module Textbringer
|
|
|
146
158
|
Curses.start_color
|
|
147
159
|
Curses.use_default_colors
|
|
148
160
|
load_faces
|
|
161
|
+
else
|
|
162
|
+
Face.define :mode_line, reverse: true
|
|
163
|
+
Face.define :region, reverse: true
|
|
164
|
+
Face.define :isearch, underline: true
|
|
149
165
|
end
|
|
150
166
|
begin
|
|
151
167
|
window =
|
|
@@ -161,6 +177,7 @@ module Textbringer
|
|
|
161
177
|
@@started = true
|
|
162
178
|
yield
|
|
163
179
|
ensure
|
|
180
|
+
Buffer.current&.input_method&.on_deactivate
|
|
164
181
|
@@list.each do |win|
|
|
165
182
|
win.close
|
|
166
183
|
end
|
|
@@ -261,7 +278,7 @@ module Textbringer
|
|
|
261
278
|
@cursor = Cursor.new(0, 0)
|
|
262
279
|
@in_region = false
|
|
263
280
|
@in_isearch = false
|
|
264
|
-
@
|
|
281
|
+
@current_hl_face = nil
|
|
265
282
|
end
|
|
266
283
|
|
|
267
284
|
def echo_area?
|
|
@@ -387,6 +404,10 @@ module Textbringer
|
|
|
387
404
|
def highlight
|
|
388
405
|
@highlight_on = {}
|
|
389
406
|
@highlight_off = {}
|
|
407
|
+
if (override = @buffer[:highlight_override])
|
|
408
|
+
@highlight_on, @highlight_off = override.call
|
|
409
|
+
return
|
|
410
|
+
end
|
|
390
411
|
return if !@@has_colors || !CONFIG[:syntax_highlight] || @buffer.binary?
|
|
391
412
|
syntax_table = @buffer.mode.syntax_table || DEFAULT_SYNTAX_TABLE
|
|
392
413
|
if @buffer.bytesize < CONFIG[:highlight_buffer_size_limit]
|
|
@@ -410,10 +431,10 @@ module Textbringer
|
|
|
410
431
|
b = @buffer.point
|
|
411
432
|
end
|
|
412
433
|
name = names.find { |n| $~[n] }
|
|
413
|
-
|
|
414
|
-
if
|
|
415
|
-
@highlight_on[b] =
|
|
416
|
-
@highlight_off[e] =
|
|
434
|
+
face = Face[name]
|
|
435
|
+
if face
|
|
436
|
+
@highlight_on[b] = face
|
|
437
|
+
@highlight_off[e] = true
|
|
417
438
|
end
|
|
418
439
|
end
|
|
419
440
|
end
|
|
@@ -434,45 +455,30 @@ module Textbringer
|
|
|
434
455
|
highlight
|
|
435
456
|
@window.erase
|
|
436
457
|
@window.setpos(0, 0)
|
|
437
|
-
@window.attrset(0)
|
|
438
458
|
@in_region = false
|
|
439
459
|
@in_isearch = false
|
|
440
|
-
@
|
|
460
|
+
@current_hl_face = nil
|
|
441
461
|
if current_or_minibuffer_selected? && @buffer.visible_mark &&
|
|
442
462
|
@buffer.point_after_mark?(@buffer.visible_mark)
|
|
443
|
-
@window.attron(region_attr)
|
|
444
463
|
@in_region = true
|
|
445
464
|
end
|
|
446
465
|
if current_or_minibuffer_selected? && @buffer.isearch_mark &&
|
|
447
466
|
@buffer.point_after_mark?(@buffer.isearch_mark)
|
|
448
|
-
|
|
449
|
-
if @in_region
|
|
450
|
-
@window.attroff(region_attr)
|
|
451
|
-
end
|
|
452
|
-
@window.attron(isearch_attr)
|
|
467
|
+
@in_region = false
|
|
453
468
|
@in_isearch = true
|
|
454
469
|
end
|
|
470
|
+
apply_window_attrs(@window)
|
|
455
471
|
while !@buffer.end_of_buffer?
|
|
456
472
|
cury = @window.cury
|
|
457
473
|
curx = @window.curx
|
|
458
474
|
update_cursor_and_attr(point, cury, curx)
|
|
459
|
-
if
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
@window.attroff(attrs & ~Curses::A_COLOR)
|
|
463
|
-
else
|
|
464
|
-
@window.attroff(attrs)
|
|
465
|
-
end
|
|
466
|
-
@current_highlight_attrs = 0
|
|
475
|
+
if @highlight_off[@buffer.point]
|
|
476
|
+
@current_hl_face = nil
|
|
477
|
+
apply_window_attrs(@window)
|
|
467
478
|
end
|
|
468
|
-
if
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
@window.attron(attrs & ~Curses::A_COLOR)
|
|
472
|
-
else
|
|
473
|
-
@window.attron(attrs)
|
|
474
|
-
end
|
|
475
|
-
@current_highlight_attrs = attrs
|
|
479
|
+
if face = @highlight_on[@buffer.point]
|
|
480
|
+
@current_hl_face = face
|
|
481
|
+
apply_window_attrs(@window)
|
|
476
482
|
end
|
|
477
483
|
c = @buffer.char_after
|
|
478
484
|
if c == "\n"
|
|
@@ -517,12 +523,7 @@ module Textbringer
|
|
|
517
523
|
break if newx == columns && cury == lines - 2
|
|
518
524
|
@buffer.forward_char
|
|
519
525
|
end
|
|
520
|
-
|
|
521
|
-
@window.attroff(isearch_attr)
|
|
522
|
-
end
|
|
523
|
-
if current_or_minibuffer_selected? && @buffer.visible_mark
|
|
524
|
-
@window.attroff(region_attr)
|
|
525
|
-
end
|
|
526
|
+
apply_window_attrs(@window)
|
|
526
527
|
@buffer.mark_to_point(@bottom_of_window)
|
|
527
528
|
if @buffer.point_at_mark?(point)
|
|
528
529
|
@cursor.y = @window.cury
|
|
@@ -712,8 +713,8 @@ module Textbringer
|
|
|
712
713
|
def redisplay_mode_line
|
|
713
714
|
@mode_line.erase
|
|
714
715
|
@mode_line.setpos(0, 0)
|
|
715
|
-
|
|
716
|
-
@mode_line.
|
|
716
|
+
face = Face[:mode_line]
|
|
717
|
+
@mode_line.attr_set(face.text_attrs, face.color_pair)
|
|
717
718
|
@mode_line.addstr("#{@buffer.input_method_status} #{@buffer.name} ")
|
|
718
719
|
@mode_line.addstr("[+]") if @buffer.modified?
|
|
719
720
|
@mode_line.addstr("[RO]") if @buffer.read_only?
|
|
@@ -731,7 +732,7 @@ module Textbringer
|
|
|
731
732
|
@mode_line.addstr(" #{line},#{column}")
|
|
732
733
|
@mode_line.addstr(" (#{@buffer.mode_names.join(' ')})")
|
|
733
734
|
@mode_line.addstr(" " * (columns - @mode_line.curx))
|
|
734
|
-
@mode_line.
|
|
735
|
+
@mode_line.attr_set(0, 0)
|
|
735
736
|
@mode_line.noutrefresh
|
|
736
737
|
end
|
|
737
738
|
|
|
@@ -746,45 +747,28 @@ module Textbringer
|
|
|
746
747
|
end
|
|
747
748
|
|
|
748
749
|
def update_cursor_and_attr(point, cury, curx)
|
|
750
|
+
changed = false
|
|
749
751
|
if @buffer.point_at_mark?(point)
|
|
750
752
|
@cursor.y = cury
|
|
751
753
|
@cursor.x = curx
|
|
752
754
|
# Handle visible mark transitions
|
|
753
755
|
if current_or_minibuffer_selected? && @buffer.visible_mark
|
|
754
756
|
if @buffer.point_after_mark?(@buffer.visible_mark)
|
|
755
|
-
unless @in_isearch
|
|
756
|
-
@window.attroff(region_attr)
|
|
757
|
-
# Restore syntax highlighting colors after exiting region
|
|
758
|
-
if @current_highlight_attrs != 0
|
|
759
|
-
@window.attron(@current_highlight_attrs)
|
|
760
|
-
end
|
|
761
|
-
end
|
|
762
757
|
@in_region = false
|
|
758
|
+
changed = true
|
|
763
759
|
elsif @buffer.point_before_mark?(@buffer.visible_mark)
|
|
764
|
-
unless @in_isearch
|
|
765
|
-
@window.attron(region_attr)
|
|
766
|
-
end
|
|
767
760
|
@in_region = true
|
|
761
|
+
changed = true
|
|
768
762
|
end
|
|
769
763
|
end
|
|
770
764
|
# Handle isearch mark transitions
|
|
771
765
|
if current_or_minibuffer_selected? && @buffer.isearch_mark
|
|
772
766
|
if @buffer.point_after_mark?(@buffer.isearch_mark)
|
|
773
|
-
@window.attroff(isearch_attr)
|
|
774
767
|
@in_isearch = false
|
|
775
|
-
|
|
776
|
-
if @in_region
|
|
777
|
-
@window.attron(region_attr)
|
|
778
|
-
elsif @current_highlight_attrs != 0
|
|
779
|
-
@window.attron(@current_highlight_attrs)
|
|
780
|
-
end
|
|
768
|
+
changed = true
|
|
781
769
|
elsif @buffer.point_before_mark?(@buffer.isearch_mark)
|
|
782
|
-
# Entering isearch - turn off region if active
|
|
783
|
-
if @in_region
|
|
784
|
-
@window.attroff(region_attr)
|
|
785
|
-
end
|
|
786
|
-
@window.attron(isearch_attr)
|
|
787
770
|
@in_isearch = true
|
|
771
|
+
changed = true
|
|
788
772
|
end
|
|
789
773
|
end
|
|
790
774
|
end
|
|
@@ -792,42 +776,25 @@ module Textbringer
|
|
|
792
776
|
if current_or_minibuffer_selected? && @buffer.isearch_mark &&
|
|
793
777
|
@buffer.point_at_mark?(@buffer.isearch_mark)
|
|
794
778
|
if @buffer.point_after_mark?(point)
|
|
795
|
-
@window.attroff(isearch_attr)
|
|
796
779
|
@in_isearch = false
|
|
797
|
-
|
|
798
|
-
if @in_region
|
|
799
|
-
@window.attron(region_attr)
|
|
800
|
-
elsif @current_highlight_attrs != 0
|
|
801
|
-
@window.attron(@current_highlight_attrs)
|
|
802
|
-
end
|
|
780
|
+
changed = true
|
|
803
781
|
elsif @buffer.point_before_mark?(point)
|
|
804
|
-
# Entering isearch - turn off region if active
|
|
805
|
-
if @in_region
|
|
806
|
-
@window.attroff(region_attr)
|
|
807
|
-
end
|
|
808
|
-
@window.attron(isearch_attr)
|
|
809
782
|
@in_isearch = true
|
|
783
|
+
changed = true
|
|
810
784
|
end
|
|
811
785
|
end
|
|
812
786
|
# Handle transitions when point crosses visible mark
|
|
813
787
|
if current_or_minibuffer_selected? && @buffer.visible_mark &&
|
|
814
788
|
@buffer.point_at_mark?(@buffer.visible_mark)
|
|
815
789
|
if @buffer.point_after_mark?(point)
|
|
816
|
-
unless @in_isearch
|
|
817
|
-
@window.attroff(region_attr)
|
|
818
|
-
# Restore syntax highlighting colors after exiting region
|
|
819
|
-
if @current_highlight_attrs != 0
|
|
820
|
-
@window.attron(@current_highlight_attrs)
|
|
821
|
-
end
|
|
822
|
-
end
|
|
823
790
|
@in_region = false
|
|
791
|
+
changed = true
|
|
824
792
|
elsif @buffer.point_before_mark?(point)
|
|
825
|
-
unless @in_isearch
|
|
826
|
-
@window.attron(region_attr)
|
|
827
|
-
end
|
|
828
793
|
@in_region = true
|
|
794
|
+
changed = true
|
|
829
795
|
end
|
|
830
796
|
end
|
|
797
|
+
apply_window_attrs(@window) if changed
|
|
831
798
|
end
|
|
832
799
|
|
|
833
800
|
def compose_character(point, cury, curx, c)
|
|
@@ -1004,12 +971,28 @@ module Textbringer
|
|
|
1004
971
|
end
|
|
1005
972
|
end
|
|
1006
973
|
|
|
1007
|
-
def
|
|
1008
|
-
|
|
974
|
+
def apply_face_attrs(win, face)
|
|
975
|
+
win.attr_set(face&.text_attrs || 0, face&.color_pair || 0)
|
|
1009
976
|
end
|
|
1010
977
|
|
|
1011
|
-
def
|
|
1012
|
-
|
|
978
|
+
def apply_window_attrs(win)
|
|
979
|
+
if @in_isearch
|
|
980
|
+
face = Face[:isearch]
|
|
981
|
+
elsif @in_region
|
|
982
|
+
face = Face[:region]
|
|
983
|
+
else
|
|
984
|
+
face = @current_hl_face
|
|
985
|
+
end
|
|
986
|
+
|
|
987
|
+
if face
|
|
988
|
+
text_attrs = face.text_attrs
|
|
989
|
+
if (@in_isearch || @in_region) && @current_hl_face
|
|
990
|
+
text_attrs |= @current_hl_face.text_attrs
|
|
991
|
+
end
|
|
992
|
+
win.attr_set(text_attrs, face.color_pair)
|
|
993
|
+
else
|
|
994
|
+
win.attr_set(0, 0)
|
|
995
|
+
end
|
|
1013
996
|
end
|
|
1014
997
|
end
|
|
1015
998
|
|
|
@@ -1057,10 +1040,10 @@ module Textbringer
|
|
|
1057
1040
|
@window.addstr(@buffer.input_method_status)
|
|
1058
1041
|
end
|
|
1059
1042
|
@window.setpos(0, 0)
|
|
1060
|
-
@window.
|
|
1043
|
+
@window.attr_set(0, 0)
|
|
1061
1044
|
@in_region = false
|
|
1062
1045
|
@in_isearch = false
|
|
1063
|
-
@
|
|
1046
|
+
@current_hl_face = nil
|
|
1064
1047
|
if @message
|
|
1065
1048
|
@window.addstr(escape(@message))
|
|
1066
1049
|
else
|
data/lib/textbringer.rb
CHANGED
|
@@ -12,6 +12,7 @@ require_relative "textbringer/face"
|
|
|
12
12
|
require_relative "textbringer/completion_popup"
|
|
13
13
|
require_relative "textbringer/lsp/client"
|
|
14
14
|
require_relative "textbringer/lsp/server_registry"
|
|
15
|
+
require_relative "textbringer/gamegrid"
|
|
15
16
|
require_relative "textbringer/commands"
|
|
16
17
|
require_relative "textbringer/commands/buffers"
|
|
17
18
|
require_relative "textbringer/commands/windows"
|
|
@@ -33,6 +34,9 @@ require_relative "textbringer/commands/ucs_normalize"
|
|
|
33
34
|
require_relative "textbringer/commands/help"
|
|
34
35
|
require_relative "textbringer/commands/completion"
|
|
35
36
|
require_relative "textbringer/commands/lsp"
|
|
37
|
+
require_relative "textbringer/commands/dired"
|
|
38
|
+
require_relative "textbringer/commands/gamegrid"
|
|
39
|
+
require_relative "textbringer/commands/tetris"
|
|
36
40
|
require_relative "textbringer/mode"
|
|
37
41
|
require_relative "textbringer/modes/fundamental_mode"
|
|
38
42
|
require_relative "textbringer/modes/programming_mode"
|
|
@@ -42,6 +46,9 @@ require_relative "textbringer/modes/backtrace_mode"
|
|
|
42
46
|
require_relative "textbringer/modes/completion_list_mode"
|
|
43
47
|
require_relative "textbringer/modes/buffer_list_mode"
|
|
44
48
|
require_relative "textbringer/modes/help_mode"
|
|
49
|
+
require_relative "textbringer/modes/dired_mode"
|
|
50
|
+
require_relative "textbringer/modes/gamegrid_mode"
|
|
51
|
+
require_relative "textbringer/modes/tetris_mode"
|
|
45
52
|
require_relative "textbringer/minor_mode"
|
|
46
53
|
require_relative "textbringer/global_minor_mode"
|
|
47
54
|
require_relative "textbringer/modes/overwrite_mode"
|