textbringer 17 → 19
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/exe/txtb +1 -1
- data/lib/textbringer/buffer.rb +37 -3
- data/lib/textbringer/commands/buffers.rb +4 -2
- data/lib/textbringer/commands/clipboard.rb +21 -6
- data/lib/textbringer/commands/completion.rb +133 -0
- data/lib/textbringer/commands/ctags.rb +1 -1
- data/lib/textbringer/commands/files.rb +11 -1
- data/lib/textbringer/commands/help.rb +1 -1
- data/lib/textbringer/commands/isearch.rb +4 -10
- data/lib/textbringer/commands/ispell.rb +0 -2
- data/lib/textbringer/commands/lsp.rb +389 -0
- data/lib/textbringer/commands/misc.rb +2 -1
- data/lib/textbringer/commands.rb +7 -3
- data/lib/textbringer/completion_popup.rb +188 -0
- data/lib/textbringer/faces/basic.rb +3 -1
- data/lib/textbringer/faces/completion.rb +4 -0
- data/lib/textbringer/floating_window.rb +327 -0
- data/lib/textbringer/input_methods/skk_input_method.rb +751 -0
- data/lib/textbringer/lsp/client.rb +568 -0
- data/lib/textbringer/lsp/server_registry.rb +138 -0
- data/lib/textbringer/mode.rb +3 -1
- data/lib/textbringer/modes/programming_mode.rb +17 -8
- data/lib/textbringer/modes/transient_mark_mode.rb +9 -2
- data/lib/textbringer/utils.rb +14 -10
- data/lib/textbringer/version.rb +1 -1
- data/lib/textbringer/window.rb +116 -19
- data/lib/textbringer.rb +7 -0
- data/sig/lib/textbringer/buffer.rbs +483 -0
- data/sig/lib/textbringer/color.rbs +9 -0
- data/sig/lib/textbringer/commands/buffers.rbs +93 -0
- data/sig/lib/textbringer/commands/clipboard.rbs +17 -0
- data/sig/lib/textbringer/commands/completion.rbs +20 -0
- data/sig/lib/textbringer/commands/ctags.rbs +11 -0
- data/sig/lib/textbringer/commands/dabbrev.rbs +4 -0
- data/sig/lib/textbringer/commands/files.rbs +29 -0
- data/sig/lib/textbringer/commands/fill.rbs +5 -0
- data/sig/lib/textbringer/commands/help.rbs +28 -0
- data/sig/lib/textbringer/commands/input_method.rbs +6 -0
- data/sig/lib/textbringer/commands/isearch.rbs +38 -0
- data/sig/lib/textbringer/commands/ispell.rbs +39 -0
- data/sig/lib/textbringer/commands/keyboard_macro.rbs +25 -0
- data/sig/lib/textbringer/commands/lsp.rbs +8 -0
- data/sig/lib/textbringer/commands/misc.rbs +74 -0
- data/sig/lib/textbringer/commands/rectangle.rbs +19 -0
- data/sig/lib/textbringer/commands/register.rbs +31 -0
- data/sig/lib/textbringer/commands/replace.rbs +17 -0
- data/sig/lib/textbringer/commands/server.rbs +31 -0
- data/sig/lib/textbringer/commands/ucs_normalize.rbs +9 -0
- data/sig/lib/textbringer/commands/windows.rbs +45 -0
- data/sig/lib/textbringer/commands.rbs +21 -0
- data/sig/lib/textbringer/completion_popup.rbs +40 -0
- data/sig/lib/textbringer/controller.rbs +58 -0
- data/sig/lib/textbringer/default_output.rbs +7 -0
- data/sig/lib/textbringer/errors.rbs +3 -0
- data/sig/lib/textbringer/face.rbs +19 -0
- data/sig/lib/textbringer/floating_window.rbs +42 -0
- data/sig/lib/textbringer/global_minor_mode.rbs +7 -0
- data/sig/lib/textbringer/input_method.rbs +28 -0
- data/sig/lib/textbringer/input_methods/hangul_input_method.rbs +12 -0
- data/sig/lib/textbringer/input_methods/hiragana_input_method.rbs +12 -0
- data/sig/lib/textbringer/input_methods/t_code_input_method.rbs +49 -0
- data/sig/lib/textbringer/keymap.rbs +33 -0
- data/sig/lib/textbringer/lsp/client.rbs +21 -0
- data/sig/lib/textbringer/lsp/server_registry.rbs +23 -0
- data/sig/lib/textbringer/minor_mode.rbs +12 -0
- data/sig/lib/textbringer/mode.rbs +70 -0
- data/sig/lib/textbringer/modes/backtrace_mode.rbs +8 -0
- data/sig/lib/textbringer/modes/buffer_list_mode.rbs +5 -0
- data/sig/lib/textbringer/modes/c_mode.rbs +21 -0
- data/sig/lib/textbringer/modes/completion_list_mode.rbs +5 -0
- data/sig/lib/textbringer/modes/fundamental_mode.rbs +3 -0
- data/sig/lib/textbringer/modes/help_mode.rbs +7 -0
- data/sig/lib/textbringer/modes/overwrite_mode.rbs +15 -0
- data/sig/lib/textbringer/modes/programming_mode.rbs +14 -0
- data/sig/lib/textbringer/modes/ruby_mode.rbs +57 -0
- data/sig/lib/textbringer/plugin.rbs +3 -0
- data/sig/lib/textbringer/ring.rbs +36 -0
- data/sig/lib/textbringer/utils.rbs +95 -0
- data/sig/lib/textbringer/window.rbs +183 -0
- data/textbringer.gemspec +1 -0
- metadata +76 -2
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
require "curses"
|
|
2
|
+
|
|
3
|
+
module Textbringer
|
|
4
|
+
class FloatingWindow < Window
|
|
5
|
+
# Class-level tracking (separate from @@list)
|
|
6
|
+
@@floating_windows = []
|
|
7
|
+
|
|
8
|
+
def self.floating_windows
|
|
9
|
+
@@floating_windows.dup
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.close_all_floating
|
|
13
|
+
@@floating_windows.dup.each(&:close)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.redisplay_all_floating
|
|
17
|
+
@@floating_windows.each do |win|
|
|
18
|
+
win.redisplay unless win.deleted?
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Initialize with dimensions and position
|
|
23
|
+
# @param lines [Integer] Height of the floating window
|
|
24
|
+
# @param columns [Integer] Width of the floating window
|
|
25
|
+
# @param y [Integer] Screen Y coordinate
|
|
26
|
+
# @param x [Integer] Screen X coordinate
|
|
27
|
+
# @param buffer [Buffer, nil] Optional buffer, creates new if nil
|
|
28
|
+
# @param face [Symbol, nil] Face name to apply to the window (default: :floating_window)
|
|
29
|
+
# @param current_line_face [Symbol, nil] Face name to apply to the line containing point
|
|
30
|
+
def initialize(lines, columns, y, x, buffer: nil, face: :floating_window, current_line_face: nil)
|
|
31
|
+
super(lines, columns, y, x)
|
|
32
|
+
|
|
33
|
+
# Create or assign buffer
|
|
34
|
+
if buffer
|
|
35
|
+
self.buffer = buffer
|
|
36
|
+
else
|
|
37
|
+
# Create a dedicated buffer for this floating window
|
|
38
|
+
name = "*floating-#{object_id}*"
|
|
39
|
+
self.buffer = Buffer.new_buffer(name, undo_limit: 0)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Store face for rendering
|
|
43
|
+
@face = face
|
|
44
|
+
@current_line_face = current_line_face
|
|
45
|
+
|
|
46
|
+
# Track this floating window
|
|
47
|
+
@@floating_windows << self
|
|
48
|
+
@visible = false
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Factory methods for common positioning patterns
|
|
52
|
+
def self.at_cursor(lines:, columns:, window: Window.current, buffer: nil, face: :floating_window, current_line_face: nil)
|
|
53
|
+
y, x = calculate_cursor_position(lines, columns, window)
|
|
54
|
+
new(lines, columns, y, x, buffer: buffer, face: face, current_line_face: current_line_face)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.centered(lines:, columns:, buffer: nil, face: :floating_window, current_line_face: nil)
|
|
58
|
+
y = (Curses.lines - lines) / 2
|
|
59
|
+
x = (Curses.cols - columns) / 2
|
|
60
|
+
new(lines, columns, y, x, buffer: buffer, face: face, current_line_face: current_line_face)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Override: Not part of main window list management
|
|
64
|
+
def echo_area?
|
|
65
|
+
false
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def active?
|
|
69
|
+
@visible && !deleted?
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def floating_window?
|
|
73
|
+
true
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Visibility management
|
|
77
|
+
def show
|
|
78
|
+
# Save current window to prevent focus change
|
|
79
|
+
old_current = Window.current
|
|
80
|
+
@visible = true
|
|
81
|
+
redisplay
|
|
82
|
+
# Restore focus to original window
|
|
83
|
+
Window.current = old_current if Window.current != old_current
|
|
84
|
+
self
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def hide
|
|
88
|
+
@visible = false
|
|
89
|
+
Window.redisplay # Refresh underlying windows
|
|
90
|
+
self
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def visible?
|
|
94
|
+
@visible && !deleted?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Override delete to clean up from floating window list
|
|
98
|
+
def delete
|
|
99
|
+
return if deleted?
|
|
100
|
+
|
|
101
|
+
@@floating_windows.delete(self)
|
|
102
|
+
@visible = false
|
|
103
|
+
|
|
104
|
+
# Delete associated buffer if auto-generated
|
|
105
|
+
if @buffer && @buffer.name.start_with?("*floating-")
|
|
106
|
+
@buffer.kill
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
super # Call Window#delete
|
|
110
|
+
|
|
111
|
+
Window.redisplay
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
alias_method :close, :delete
|
|
115
|
+
|
|
116
|
+
# Move to new position
|
|
117
|
+
def move_to(y:, x:)
|
|
118
|
+
@y = y
|
|
119
|
+
@x = x
|
|
120
|
+
redisplay if visible?
|
|
121
|
+
self
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Resize window
|
|
125
|
+
def resize(lines, columns)
|
|
126
|
+
@lines = lines
|
|
127
|
+
@columns = columns
|
|
128
|
+
|
|
129
|
+
# Recreate pad with new size
|
|
130
|
+
old_window = @window
|
|
131
|
+
initialize_window(lines, columns, @y, @x)
|
|
132
|
+
old_window.close if old_window.respond_to?(:close)
|
|
133
|
+
|
|
134
|
+
redisplay if visible?
|
|
135
|
+
self
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Override redisplay to use pad refresh
|
|
139
|
+
def redisplay
|
|
140
|
+
return if @buffer.nil? || !@visible || deleted?
|
|
141
|
+
|
|
142
|
+
@buffer.save_point do |point|
|
|
143
|
+
@window.erase
|
|
144
|
+
|
|
145
|
+
# Get face attributes if face is specified
|
|
146
|
+
face_attrs = 0
|
|
147
|
+
if @face && Window.has_colors?
|
|
148
|
+
face = Face[@face]
|
|
149
|
+
face_attrs = face.attributes if face
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Get current line face attributes if specified
|
|
153
|
+
current_line_attrs = 0
|
|
154
|
+
if @current_line_face && Window.has_colors?
|
|
155
|
+
current_line_face = Face[@current_line_face]
|
|
156
|
+
current_line_attrs = current_line_face.attributes if current_line_face
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
@window.attrset(face_attrs)
|
|
160
|
+
@in_region = false
|
|
161
|
+
@in_isearch = false
|
|
162
|
+
@current_highlight_attrs = face_attrs
|
|
163
|
+
|
|
164
|
+
# First pass: find which line contains point
|
|
165
|
+
point_line = nil
|
|
166
|
+
point_pos = point.location
|
|
167
|
+
@buffer.point_to_mark(@top_of_window)
|
|
168
|
+
line_num = 0
|
|
169
|
+
while line_num < @lines && !@buffer.end_of_buffer?
|
|
170
|
+
line_start = @buffer.point
|
|
171
|
+
# Move to end of line or end of buffer
|
|
172
|
+
while !@buffer.end_of_buffer?
|
|
173
|
+
c = @buffer.char_after
|
|
174
|
+
break if c.nil? || c == "\n"
|
|
175
|
+
@buffer.forward_char
|
|
176
|
+
end
|
|
177
|
+
line_end = @buffer.point
|
|
178
|
+
@buffer.forward_char unless @buffer.end_of_buffer? # Skip newline
|
|
179
|
+
|
|
180
|
+
# Check if point is on this line
|
|
181
|
+
if point_pos >= line_start && point_pos <= line_end
|
|
182
|
+
point_line = line_num
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
line_num += 1
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Start from top of window for actual rendering
|
|
189
|
+
@buffer.point_to_mark(@top_of_window)
|
|
190
|
+
@cursor.y = @cursor.x = 0
|
|
191
|
+
|
|
192
|
+
# Render lines
|
|
193
|
+
line_num = 0
|
|
194
|
+
while line_num < @lines && !@buffer.end_of_buffer?
|
|
195
|
+
@window.setpos(line_num, 0)
|
|
196
|
+
|
|
197
|
+
# Determine which face to use for this line
|
|
198
|
+
line_attrs = if @current_line_face && line_num == point_line
|
|
199
|
+
current_line_attrs
|
|
200
|
+
else
|
|
201
|
+
face_attrs
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Render characters on this line
|
|
205
|
+
col = 0
|
|
206
|
+
while col < @columns && !@buffer.end_of_buffer?
|
|
207
|
+
cury = @window.cury
|
|
208
|
+
curx = @window.curx
|
|
209
|
+
|
|
210
|
+
# Apply face attributes without modifying cursor tracking
|
|
211
|
+
if @buffer.point_at_mark?(point)
|
|
212
|
+
@cursor.y = cury
|
|
213
|
+
@cursor.x = curx
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
c = @buffer.char_after
|
|
217
|
+
break if c.nil?
|
|
218
|
+
|
|
219
|
+
if c == "\n"
|
|
220
|
+
@buffer.forward_char
|
|
221
|
+
break
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
s = escape(c)
|
|
225
|
+
char_width = Buffer.display_width(s)
|
|
226
|
+
|
|
227
|
+
if col + char_width > @columns
|
|
228
|
+
break
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Apply face attributes to all characters
|
|
232
|
+
if line_attrs != 0
|
|
233
|
+
@window.attron(line_attrs)
|
|
234
|
+
end
|
|
235
|
+
@window.addstr(s)
|
|
236
|
+
if line_attrs != 0
|
|
237
|
+
@window.attroff(line_attrs)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
col += char_width
|
|
241
|
+
@buffer.forward_char
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Fill remaining space on the line with the face background
|
|
245
|
+
if line_attrs != 0 && col < @columns
|
|
246
|
+
@window.attron(line_attrs)
|
|
247
|
+
@window.addstr(" " * (@columns - col))
|
|
248
|
+
@window.attroff(line_attrs)
|
|
249
|
+
elsif line_attrs == 0 && face_attrs != 0 && col < @columns
|
|
250
|
+
# Use default face for padding if no line-specific attrs
|
|
251
|
+
@window.attron(face_attrs)
|
|
252
|
+
@window.addstr(" " * (@columns - col))
|
|
253
|
+
@window.attroff(face_attrs)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Track cursor position
|
|
257
|
+
if @buffer.point_at_mark?(point)
|
|
258
|
+
@cursor.y = line_num
|
|
259
|
+
@cursor.x = col
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
line_num += 1
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Fill remaining lines with the face background
|
|
266
|
+
if face_attrs != 0
|
|
267
|
+
while line_num < @lines
|
|
268
|
+
@window.setpos(line_num, 0)
|
|
269
|
+
@window.attron(face_attrs)
|
|
270
|
+
@window.addstr(" " * @columns)
|
|
271
|
+
@window.attroff(face_attrs)
|
|
272
|
+
line_num += 1
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Don't set cursor position - FloatingWindow should not affect screen cursor
|
|
277
|
+
# The cursor stays in the original window that had focus
|
|
278
|
+
|
|
279
|
+
# Refresh pad to screen
|
|
280
|
+
# noutrefresh(pad_min_y, pad_min_x, screen_min_y, screen_min_x, screen_max_y, screen_max_x)
|
|
281
|
+
@window.noutrefresh(
|
|
282
|
+
0, 0, # Start of pad
|
|
283
|
+
@y, @x, # Screen position
|
|
284
|
+
@y + @lines - 1, @x + @columns - 1 # Screen extent
|
|
285
|
+
)
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
private
|
|
290
|
+
|
|
291
|
+
# Override to create Curses::Pad instead of Curses::Window
|
|
292
|
+
def initialize_window(num_lines, num_columns, y, x)
|
|
293
|
+
@window = Curses::Pad.new(num_lines, num_columns)
|
|
294
|
+
# Note: Pad position is set during refresh, not at creation
|
|
295
|
+
# No mode_line for floating windows
|
|
296
|
+
@mode_line = nil
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def self.calculate_cursor_position(lines, columns, window)
|
|
300
|
+
# Get cursor screen coordinates
|
|
301
|
+
cursor_y = window.y + window.cursor.y
|
|
302
|
+
cursor_x = window.x + window.cursor.x
|
|
303
|
+
|
|
304
|
+
# Prefer below cursor
|
|
305
|
+
space_below = Curses.lines - cursor_y - 2 # -2 for echo area
|
|
306
|
+
space_above = cursor_y # Screen space above cursor
|
|
307
|
+
|
|
308
|
+
if space_below >= lines
|
|
309
|
+
y = cursor_y + 1
|
|
310
|
+
elsif space_above >= lines
|
|
311
|
+
y = cursor_y - lines
|
|
312
|
+
else
|
|
313
|
+
# Not enough space, show below and clip
|
|
314
|
+
y = [cursor_y + 1, Curses.lines - lines - 1].max
|
|
315
|
+
y = [y, 0].max
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Adjust x to prevent overflow
|
|
319
|
+
x = cursor_x
|
|
320
|
+
if x + columns > Curses.cols
|
|
321
|
+
x = [Curses.cols - columns, 0].max
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
[y, x]
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|