textbringer 18 → 20

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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/exe/txtb +1 -1
  3. data/lib/textbringer/buffer.rb +23 -2
  4. data/lib/textbringer/commands/clipboard.rb +21 -6
  5. data/lib/textbringer/commands/completion.rb +133 -0
  6. data/lib/textbringer/commands/ctags.rb +1 -1
  7. data/lib/textbringer/commands/files.rb +11 -1
  8. data/lib/textbringer/commands/help.rb +1 -1
  9. data/lib/textbringer/commands/ispell.rb +0 -2
  10. data/lib/textbringer/commands/lsp.rb +389 -0
  11. data/lib/textbringer/commands/misc.rb +2 -1
  12. data/lib/textbringer/commands.rb +7 -3
  13. data/lib/textbringer/completion_popup.rb +188 -0
  14. data/lib/textbringer/faces/basic.rb +1 -0
  15. data/lib/textbringer/faces/completion.rb +4 -0
  16. data/lib/textbringer/floating_window.rb +327 -0
  17. data/lib/textbringer/input_methods/skk_input_method.rb +773 -0
  18. data/lib/textbringer/lsp/client.rb +568 -0
  19. data/lib/textbringer/lsp/server_registry.rb +138 -0
  20. data/lib/textbringer/mode.rb +3 -1
  21. data/lib/textbringer/modes/programming_mode.rb +17 -8
  22. data/lib/textbringer/modes/transient_mark_mode.rb +5 -2
  23. data/lib/textbringer/utils.rb +14 -10
  24. data/lib/textbringer/version.rb +1 -1
  25. data/lib/textbringer/window.rb +36 -9
  26. data/lib/textbringer.rb +7 -0
  27. data/sig/lib/textbringer/buffer.rbs +483 -0
  28. data/sig/lib/textbringer/color.rbs +9 -0
  29. data/sig/lib/textbringer/commands/buffers.rbs +93 -0
  30. data/sig/lib/textbringer/commands/clipboard.rbs +17 -0
  31. data/sig/lib/textbringer/commands/completion.rbs +20 -0
  32. data/sig/lib/textbringer/commands/ctags.rbs +11 -0
  33. data/sig/lib/textbringer/commands/dabbrev.rbs +4 -0
  34. data/sig/lib/textbringer/commands/files.rbs +29 -0
  35. data/sig/lib/textbringer/commands/fill.rbs +5 -0
  36. data/sig/lib/textbringer/commands/help.rbs +28 -0
  37. data/sig/lib/textbringer/commands/input_method.rbs +6 -0
  38. data/sig/lib/textbringer/commands/isearch.rbs +38 -0
  39. data/sig/lib/textbringer/commands/ispell.rbs +39 -0
  40. data/sig/lib/textbringer/commands/keyboard_macro.rbs +25 -0
  41. data/sig/lib/textbringer/commands/lsp.rbs +8 -0
  42. data/sig/lib/textbringer/commands/misc.rbs +74 -0
  43. data/sig/lib/textbringer/commands/rectangle.rbs +19 -0
  44. data/sig/lib/textbringer/commands/register.rbs +31 -0
  45. data/sig/lib/textbringer/commands/replace.rbs +17 -0
  46. data/sig/lib/textbringer/commands/server.rbs +31 -0
  47. data/sig/lib/textbringer/commands/ucs_normalize.rbs +9 -0
  48. data/sig/lib/textbringer/commands/windows.rbs +45 -0
  49. data/sig/lib/textbringer/commands.rbs +21 -0
  50. data/sig/lib/textbringer/completion_popup.rbs +40 -0
  51. data/sig/lib/textbringer/controller.rbs +58 -0
  52. data/sig/lib/textbringer/default_output.rbs +7 -0
  53. data/sig/lib/textbringer/errors.rbs +3 -0
  54. data/sig/lib/textbringer/face.rbs +19 -0
  55. data/sig/lib/textbringer/floating_window.rbs +42 -0
  56. data/sig/lib/textbringer/global_minor_mode.rbs +7 -0
  57. data/sig/lib/textbringer/input_method.rbs +28 -0
  58. data/sig/lib/textbringer/input_methods/hangul_input_method.rbs +12 -0
  59. data/sig/lib/textbringer/input_methods/hiragana_input_method.rbs +12 -0
  60. data/sig/lib/textbringer/input_methods/t_code_input_method.rbs +49 -0
  61. data/sig/lib/textbringer/keymap.rbs +33 -0
  62. data/sig/lib/textbringer/lsp/client.rbs +21 -0
  63. data/sig/lib/textbringer/lsp/server_registry.rbs +23 -0
  64. data/sig/lib/textbringer/minor_mode.rbs +12 -0
  65. data/sig/lib/textbringer/mode.rbs +70 -0
  66. data/sig/lib/textbringer/modes/backtrace_mode.rbs +8 -0
  67. data/sig/lib/textbringer/modes/buffer_list_mode.rbs +5 -0
  68. data/sig/lib/textbringer/modes/c_mode.rbs +21 -0
  69. data/sig/lib/textbringer/modes/completion_list_mode.rbs +5 -0
  70. data/sig/lib/textbringer/modes/fundamental_mode.rbs +3 -0
  71. data/sig/lib/textbringer/modes/help_mode.rbs +7 -0
  72. data/sig/lib/textbringer/modes/overwrite_mode.rbs +15 -0
  73. data/sig/lib/textbringer/modes/programming_mode.rbs +14 -0
  74. data/sig/lib/textbringer/modes/ruby_mode.rbs +57 -0
  75. data/sig/lib/textbringer/plugin.rbs +3 -0
  76. data/sig/lib/textbringer/ring.rbs +36 -0
  77. data/sig/lib/textbringer/utils.rbs +95 -0
  78. data/sig/lib/textbringer/window.rbs +183 -0
  79. data/textbringer.gemspec +1 -0
  80. 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