textbringer 13 → 15
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 +197 -0
- data/lib/textbringer/buffer.rb +28 -204
- data/lib/textbringer/commands/buffers.rb +10 -31
- data/lib/textbringer/commands/isearch.rb +9 -3
- data/lib/textbringer/commands/ispell.rb +236 -0
- data/lib/textbringer/commands/misc.rb +4 -7
- data/lib/textbringer/commands/rectangle.rb +290 -0
- data/lib/textbringer/commands/replace.rb +8 -2
- data/lib/textbringer/config.rb +1 -0
- data/lib/textbringer/global_minor_mode.rb +58 -0
- data/lib/textbringer/keymap.rb +2 -0
- data/lib/textbringer/modes/completion_list_mode.rb +1 -4
- data/lib/textbringer/modes/transient_mark_mode.rb +101 -0
- data/lib/textbringer/utils.rb +8 -6
- data/lib/textbringer/version.rb +1 -1
- data/lib/textbringer.rb +4 -0
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5ad5b7d9a5cc2022075f802db23f24781dcb3be366a4ba3ee27015a66ada834d
|
|
4
|
+
data.tar.gz: 1933c54c72c4e189b4f0a6ba81d6186d7618c9eaaa3eccbe5b61925d8959c108
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 44736e20de4174e865ad160107e48461e26d46cd495e74000138c724753dafaae65b5e530902e8f1ad1f38659e301cfe2fbec71208913f85d01af5e23b124bc5
|
|
7
|
+
data.tar.gz: b07e7c3533f540b88157c268d498f7ec6bd077db6fce0e3adb154acc02fb9d960cee0bfb23cd91342a6dce12f0c8af1083dac1434fc67c9fddc361bd03623ded
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Textbringer is an Emacs-like text editor written in Ruby. It is extensible by Ruby instead of Lisp and runs in the terminal using ncurses.
|
|
8
|
+
|
|
9
|
+
**Ruby Version**: Requires Ruby >= 3.2
|
|
10
|
+
|
|
11
|
+
## Development Commands
|
|
12
|
+
|
|
13
|
+
### Setup
|
|
14
|
+
```bash
|
|
15
|
+
bundle install
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
For ncursesw support (required for multibyte characters):
|
|
19
|
+
```bash
|
|
20
|
+
sudo apt-get install libncursesw5-dev
|
|
21
|
+
gem install curses
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Running the Editor
|
|
25
|
+
```bash
|
|
26
|
+
# Run the main executable
|
|
27
|
+
./exe/txtb
|
|
28
|
+
|
|
29
|
+
# Or after installation
|
|
30
|
+
txtb
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Testing
|
|
34
|
+
```bash
|
|
35
|
+
# Run all tests
|
|
36
|
+
bundle exec rake test
|
|
37
|
+
|
|
38
|
+
# Or simply (default task)
|
|
39
|
+
bundle exec rake
|
|
40
|
+
|
|
41
|
+
# On Ubuntu/Linux (for CI)
|
|
42
|
+
xvfb-run bundle exec rake test
|
|
43
|
+
|
|
44
|
+
# Run a single test file
|
|
45
|
+
ruby -Ilib:test test/textbringer/test_buffer.rb
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Build and Release
|
|
49
|
+
```bash
|
|
50
|
+
# Install gem locally
|
|
51
|
+
bundle exec rake install
|
|
52
|
+
|
|
53
|
+
# Bump version and create release
|
|
54
|
+
bundle exec rake bump
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Architecture
|
|
58
|
+
|
|
59
|
+
### Core Components
|
|
60
|
+
|
|
61
|
+
**Buffer** (`lib/textbringer/buffer.rb`)
|
|
62
|
+
- The fundamental text container, similar to Emacs buffers
|
|
63
|
+
- Uses a gap buffer implementation (GAP_SIZE = 256) for efficient text editing
|
|
64
|
+
- Supports undo/redo with UNDO_LIMIT = 1000
|
|
65
|
+
- Handles encoding detection (UTF-8, EUC-JP, Windows-31J) and file format conversion
|
|
66
|
+
- Manages marks, point (cursor position), and the kill ring
|
|
67
|
+
- Class methods maintain global buffer list (@@list, @@current, @@minibuffer)
|
|
68
|
+
|
|
69
|
+
**Window** (`lib/textbringer/window.rb`)
|
|
70
|
+
- Display abstraction using curses for terminal UI
|
|
71
|
+
- Multiple windows can display different buffers or the same buffer
|
|
72
|
+
- Window.current tracks the active window
|
|
73
|
+
- Echo area (@@echo_area) for messages and minibuffer input
|
|
74
|
+
- Manages cursor position and window splitting/deletion
|
|
75
|
+
|
|
76
|
+
**Controller** (`lib/textbringer/controller.rb`)
|
|
77
|
+
- The main event loop and command dispatcher
|
|
78
|
+
- Reads key sequences and dispatches to commands
|
|
79
|
+
- Handles prefix arguments, keyboard macros, and recursive editing
|
|
80
|
+
- Maintains command execution state (this_command, last_command)
|
|
81
|
+
- Pre/post command hooks for extensibility
|
|
82
|
+
|
|
83
|
+
**Mode** (`lib/textbringer/mode.rb`)
|
|
84
|
+
- Buffer modes define context-specific behavior and syntax highlighting
|
|
85
|
+
- Modes inherit from Mode class (FundamentalMode, ProgrammingMode, RubyMode, CMode, etc.)
|
|
86
|
+
- Each mode has its own keymap and syntax table
|
|
87
|
+
- `define_local_command` creates mode-specific commands
|
|
88
|
+
- Modes are automatically selected based on file_name_pattern or interpreter_name_pattern
|
|
89
|
+
|
|
90
|
+
**Keymap** (`lib/textbringer/keymap.rb`)
|
|
91
|
+
- Tree structure for key bindings (supports multi-stroke sequences)
|
|
92
|
+
- Uses `kbd()` function to parse Emacs-style key notation
|
|
93
|
+
- Key sequences can bind to commands (symbols) or nested keymaps
|
|
94
|
+
|
|
95
|
+
**Commands** (`lib/textbringer/commands.rb` and `lib/textbringer/commands/*.rb`)
|
|
96
|
+
- Commands are defined using `define_command(name, doc:)`
|
|
97
|
+
- Available as module functions in the Commands module
|
|
98
|
+
- Command groups: buffers, windows, files, isearch, replace, rectangle, etc.
|
|
99
|
+
- All commands accessible via Alt+x or key bindings
|
|
100
|
+
|
|
101
|
+
### Plugin System
|
|
102
|
+
|
|
103
|
+
Plugins are loaded from `~/.textbringer/plugins/` via `Plugin.load_plugins`. Examples:
|
|
104
|
+
- Mournmail (mail client)
|
|
105
|
+
- MedicineShield (Mastodon client)
|
|
106
|
+
- textbringer-presentation
|
|
107
|
+
- textbringer-ghost_text
|
|
108
|
+
|
|
109
|
+
### Configuration
|
|
110
|
+
|
|
111
|
+
User configuration is loaded from:
|
|
112
|
+
1. `~/.textbringer/init.rb` (loaded first)
|
|
113
|
+
2. `~/.textbringer.rb` (loaded after plugins)
|
|
114
|
+
|
|
115
|
+
Global configuration hash: `CONFIG` in `lib/textbringer/config.rb`
|
|
116
|
+
|
|
117
|
+
Key settings:
|
|
118
|
+
- `east_asian_ambiguous_width`: Character width (1 or 2)
|
|
119
|
+
- `tab_width`, `indent_tabs_mode`: Indentation
|
|
120
|
+
- `syntax_highlight`, `highlight_buffer_size_limit`: Syntax highlighting
|
|
121
|
+
- `default_input_method`: Input method for non-ASCII text
|
|
122
|
+
|
|
123
|
+
### Input Methods
|
|
124
|
+
|
|
125
|
+
Support for non-ASCII input:
|
|
126
|
+
- T-Code (`lib/textbringer/input_methods/t_code_input_method.rb`)
|
|
127
|
+
- Hiragana (`lib/textbringer/input_methods/hiragana_input_method.rb`)
|
|
128
|
+
- Hangul (`lib/textbringer/input_methods/hangul_input_method.rb`)
|
|
129
|
+
|
|
130
|
+
### Testing Infrastructure
|
|
131
|
+
|
|
132
|
+
Tests use `Test::Unit` with custom `Textbringer::TestCase` base class in `test/test_helper.rb`.
|
|
133
|
+
|
|
134
|
+
Key test helpers:
|
|
135
|
+
- `FakeController`: Test controller with `test_key_buffer` for simulating input
|
|
136
|
+
- `FakeCursesWindow`: Mock curses window for headless testing
|
|
137
|
+
- `push_keys(keys)`: Simulate keyboard input
|
|
138
|
+
- `mkcdtmpdir`: Create temporary directory for file tests
|
|
139
|
+
- `Window.setup_for_test`: Initialize test environment
|
|
140
|
+
|
|
141
|
+
Tests are organized mirroring lib structure: `test/textbringer/**/*`.
|
|
142
|
+
|
|
143
|
+
## Code Patterns
|
|
144
|
+
|
|
145
|
+
### Defining Commands
|
|
146
|
+
```ruby
|
|
147
|
+
define_command(:command_name, doc: "Description") do
|
|
148
|
+
# Command implementation
|
|
149
|
+
# Access current buffer: Buffer.current
|
|
150
|
+
# Get prefix arg: current_prefix_arg
|
|
151
|
+
end
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Mode-Specific Commands
|
|
155
|
+
```ruby
|
|
156
|
+
class MyMode < Mode
|
|
157
|
+
define_local_command(:my_command) do
|
|
158
|
+
# Mode-specific implementation
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Key Bindings
|
|
164
|
+
```ruby
|
|
165
|
+
GLOBAL_MAP.define_key("\C-x\C-f", :find_file)
|
|
166
|
+
MODE_MAP.define_key("C-c C-c", :compile)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Buffer Operations
|
|
170
|
+
- Always use `Buffer.current` to get the active buffer
|
|
171
|
+
- `@buffer.point` is the cursor position
|
|
172
|
+
- `@buffer.mark` for region operations
|
|
173
|
+
- `@buffer.insert(text)`, `@buffer.delete_char(n)` for modifications
|
|
174
|
+
- `@buffer.save_point` and `@buffer.goto_char(pos)` for navigation
|
|
175
|
+
|
|
176
|
+
### Window Management
|
|
177
|
+
- `Window.current` is the active window
|
|
178
|
+
- `Window.redisplay` updates the display
|
|
179
|
+
- `Window.echo_area` for messages
|
|
180
|
+
- `message(text)` to display in echo area
|
|
181
|
+
|
|
182
|
+
## File Organization
|
|
183
|
+
|
|
184
|
+
- `lib/textbringer.rb`: Main entry point, requires all components
|
|
185
|
+
- `lib/textbringer/commands/*.rb`: Command implementations by category
|
|
186
|
+
- `lib/textbringer/modes/*.rb`: Major and minor modes
|
|
187
|
+
- `lib/textbringer/faces/*.rb`: Syntax highlighting face definitions
|
|
188
|
+
- `exe/txtb`: Main executable
|
|
189
|
+
- `exe/tbclient`: Client for server mode
|
|
190
|
+
- `exe/tbtags`: Tag file generator
|
|
191
|
+
|
|
192
|
+
## Notes
|
|
193
|
+
|
|
194
|
+
- The editor is designed to mimic Emacs conventions and terminology
|
|
195
|
+
- Key sequences use Emacs notation: C- (Control), M- (Meta/Alt), S- (Shift)
|
|
196
|
+
- The codebase uses extensive metaprogramming for command registration and mode definition
|
|
197
|
+
- All user-facing text editing operations should go through Buffer methods to maintain undo/redo support
|
data/lib/textbringer/buffer.rb
CHANGED
|
@@ -10,7 +10,8 @@ module Textbringer
|
|
|
10
10
|
|
|
11
11
|
attr_accessor :mode, :keymap
|
|
12
12
|
attr_reader :name, :file_name, :file_encoding, :file_format, :point, :marks
|
|
13
|
-
attr_reader :current_line, :current_column, :visible_mark
|
|
13
|
+
attr_reader :current_line, :current_column, :visible_mark, :mark_active
|
|
14
|
+
attr_reader :last_match
|
|
14
15
|
attr_reader :input_method
|
|
15
16
|
|
|
16
17
|
GAP_SIZE = 256
|
|
@@ -253,8 +254,10 @@ module Textbringer
|
|
|
253
254
|
@save_point_level = 0
|
|
254
255
|
@match_offsets = []
|
|
255
256
|
@visible_mark = nil
|
|
257
|
+
@mark_active = false
|
|
256
258
|
@read_only = read_only
|
|
257
259
|
@callbacks = {}
|
|
260
|
+
@last_match = nil
|
|
258
261
|
@input_method = nil
|
|
259
262
|
end
|
|
260
263
|
|
|
@@ -945,6 +948,20 @@ module Textbringer
|
|
|
945
948
|
end
|
|
946
949
|
end
|
|
947
950
|
|
|
951
|
+
def activate_mark
|
|
952
|
+
@mark_active = true
|
|
953
|
+
set_visible_mark(@mark.location) if @mark
|
|
954
|
+
end
|
|
955
|
+
|
|
956
|
+
def deactivate_mark
|
|
957
|
+
@mark_active = false
|
|
958
|
+
delete_visible_mark
|
|
959
|
+
end
|
|
960
|
+
|
|
961
|
+
def mark_active?
|
|
962
|
+
@mark_active
|
|
963
|
+
end
|
|
964
|
+
|
|
948
965
|
def self.region_boundaries(s, e)
|
|
949
966
|
if s > e
|
|
950
967
|
[e, s]
|
|
@@ -1090,203 +1107,6 @@ module Textbringer
|
|
|
1090
1107
|
insert_for_yank(KILL_RING.rotate(1))
|
|
1091
1108
|
end
|
|
1092
1109
|
|
|
1093
|
-
# Returns start_line, start_col, end_line, and end_col of the rectangle region
|
|
1094
|
-
# Note that start_col and end_col are 0-origin and width-based (neither 1-origin nor codepoint-based)
|
|
1095
|
-
def rectangle_boundaries(s = @point, e = mark)
|
|
1096
|
-
s, e = Buffer.region_boundaries(s, e)
|
|
1097
|
-
save_excursion do
|
|
1098
|
-
goto_char(s)
|
|
1099
|
-
start_line = @current_line
|
|
1100
|
-
beginning_of_line
|
|
1101
|
-
start_col = display_width(substring(@point, s))
|
|
1102
|
-
goto_char(e)
|
|
1103
|
-
end_line = @current_line
|
|
1104
|
-
beginning_of_line
|
|
1105
|
-
end_col = display_width(substring(@point, e))
|
|
1106
|
-
|
|
1107
|
-
# Ensure start_col <= end_col
|
|
1108
|
-
if start_col > end_col
|
|
1109
|
-
start_col, end_col = end_col, start_col
|
|
1110
|
-
end
|
|
1111
|
-
[start_line, start_col, end_line, end_col]
|
|
1112
|
-
end
|
|
1113
|
-
end
|
|
1114
|
-
|
|
1115
|
-
def apply_on_rectangle(s = @point, e = mark, reverse: false)
|
|
1116
|
-
start_line, start_col, end_line, end_col = rectangle_boundaries(s, e)
|
|
1117
|
-
|
|
1118
|
-
save_excursion do
|
|
1119
|
-
composite_edit do
|
|
1120
|
-
if reverse
|
|
1121
|
-
goto_line(end_line)
|
|
1122
|
-
else
|
|
1123
|
-
goto_line(start_line)
|
|
1124
|
-
end
|
|
1125
|
-
|
|
1126
|
-
loop do
|
|
1127
|
-
beginning_of_line
|
|
1128
|
-
line_start = @point
|
|
1129
|
-
|
|
1130
|
-
# Move to start column
|
|
1131
|
-
col = 0
|
|
1132
|
-
while col < start_col && !end_of_line?
|
|
1133
|
-
forward_char
|
|
1134
|
-
col = display_width(substring(line_start, @point))
|
|
1135
|
-
end
|
|
1136
|
-
|
|
1137
|
-
yield(start_col, end_col, col, line_start)
|
|
1138
|
-
|
|
1139
|
-
# Move to next line for forward iteration
|
|
1140
|
-
if reverse
|
|
1141
|
-
break if @current_line <= start_line
|
|
1142
|
-
backward_line
|
|
1143
|
-
else
|
|
1144
|
-
break if @current_line >= end_line
|
|
1145
|
-
forward_line
|
|
1146
|
-
end
|
|
1147
|
-
end
|
|
1148
|
-
end
|
|
1149
|
-
end
|
|
1150
|
-
end
|
|
1151
|
-
|
|
1152
|
-
def extract_rectangle(s = @point, e = mark)
|
|
1153
|
-
lines = []
|
|
1154
|
-
apply_on_rectangle(s, e) do |start_col, end_col, col, line_start|
|
|
1155
|
-
start_pos = @point
|
|
1156
|
-
width = end_col - start_col
|
|
1157
|
-
|
|
1158
|
-
# If we haven't reached start_col, the line is too short
|
|
1159
|
-
if col < start_col
|
|
1160
|
-
# Line is shorter than start column, extract all spaces
|
|
1161
|
-
lines << " " * width
|
|
1162
|
-
else
|
|
1163
|
-
# Move to end column
|
|
1164
|
-
while col < end_col && !end_of_line?
|
|
1165
|
-
forward_char
|
|
1166
|
-
col = display_width(substring(line_start, @point))
|
|
1167
|
-
end
|
|
1168
|
-
end_pos = @point
|
|
1169
|
-
|
|
1170
|
-
# Extract the rectangle text for this line
|
|
1171
|
-
if end_pos > start_pos
|
|
1172
|
-
extracted = substring(start_pos, end_pos)
|
|
1173
|
-
# Pad with spaces if the extracted text is shorter than rectangle width
|
|
1174
|
-
extracted_width = display_width(extracted)
|
|
1175
|
-
if extracted_width < width
|
|
1176
|
-
extracted += " " * (width - extracted_width)
|
|
1177
|
-
end
|
|
1178
|
-
lines << extracted
|
|
1179
|
-
else
|
|
1180
|
-
lines << " " * width
|
|
1181
|
-
end
|
|
1182
|
-
end
|
|
1183
|
-
end
|
|
1184
|
-
|
|
1185
|
-
lines
|
|
1186
|
-
end
|
|
1187
|
-
|
|
1188
|
-
def copy_rectangle(s = @point, e = mark)
|
|
1189
|
-
lines = extract_rectangle(s, e)
|
|
1190
|
-
@@killed_rectangle = lines
|
|
1191
|
-
end
|
|
1192
|
-
|
|
1193
|
-
def kill_rectangle(s = @point, e = mark)
|
|
1194
|
-
copy_rectangle(s, e)
|
|
1195
|
-
delete_rectangle(s, e)
|
|
1196
|
-
end
|
|
1197
|
-
|
|
1198
|
-
def delete_rectangle(s = @point, e = mark)
|
|
1199
|
-
check_read_only_flag
|
|
1200
|
-
|
|
1201
|
-
apply_on_rectangle(s, e, reverse: true) do |start_col, end_col, col, line_start|
|
|
1202
|
-
start_pos = @point
|
|
1203
|
-
|
|
1204
|
-
# Only delete if we're within the line bounds
|
|
1205
|
-
if col >= start_col
|
|
1206
|
-
# Move to end column
|
|
1207
|
-
while col < end_col && !end_of_line?
|
|
1208
|
-
forward_char
|
|
1209
|
-
col = display_width(substring(line_start, @point))
|
|
1210
|
-
end
|
|
1211
|
-
end_pos = @point
|
|
1212
|
-
|
|
1213
|
-
# Delete the rectangle text for this line
|
|
1214
|
-
if end_pos > start_pos
|
|
1215
|
-
delete_region(start_pos, end_pos)
|
|
1216
|
-
end
|
|
1217
|
-
end
|
|
1218
|
-
end
|
|
1219
|
-
end
|
|
1220
|
-
|
|
1221
|
-
def yank_rectangle
|
|
1222
|
-
raise "No rectangle in kill ring" if @@killed_rectangle.nil?
|
|
1223
|
-
lines = @@killed_rectangle
|
|
1224
|
-
start_line = @current_line
|
|
1225
|
-
start_point = @point
|
|
1226
|
-
start_col = save_excursion {
|
|
1227
|
-
beginning_of_line
|
|
1228
|
-
display_width(substring(@point, start_point))
|
|
1229
|
-
}
|
|
1230
|
-
composite_edit do
|
|
1231
|
-
lines.each_with_index do |line, i|
|
|
1232
|
-
goto_line(start_line + i)
|
|
1233
|
-
beginning_of_line
|
|
1234
|
-
line_start = @point
|
|
1235
|
-
|
|
1236
|
-
# Move to start column, extending line if necessary
|
|
1237
|
-
col = 0
|
|
1238
|
-
while col < start_col && !end_of_line?
|
|
1239
|
-
forward_char
|
|
1240
|
-
col = display_width(substring(line_start, @point))
|
|
1241
|
-
end
|
|
1242
|
-
|
|
1243
|
-
# If line is shorter than start_col, extend it with spaces
|
|
1244
|
-
if col < start_col
|
|
1245
|
-
insert(" " * (start_col - col))
|
|
1246
|
-
end
|
|
1247
|
-
|
|
1248
|
-
# Insert the rectangle line
|
|
1249
|
-
insert(line)
|
|
1250
|
-
end
|
|
1251
|
-
end
|
|
1252
|
-
end
|
|
1253
|
-
|
|
1254
|
-
def open_rectangle(s = @point, e = mark)
|
|
1255
|
-
check_read_only_flag
|
|
1256
|
-
s, e = Buffer.region_boundaries(s, e)
|
|
1257
|
-
composite_edit do
|
|
1258
|
-
apply_on_rectangle(s, e) do |start_col, end_col, col, line_start|
|
|
1259
|
-
# If line is shorter than start_col, extend it with spaces
|
|
1260
|
-
if col < start_col
|
|
1261
|
-
insert(" " * (start_col - col))
|
|
1262
|
-
end
|
|
1263
|
-
|
|
1264
|
-
# Insert spaces to create the rectangle
|
|
1265
|
-
insert(" " * (end_col - start_col))
|
|
1266
|
-
end
|
|
1267
|
-
goto_char(s)
|
|
1268
|
-
end
|
|
1269
|
-
end
|
|
1270
|
-
|
|
1271
|
-
def clear_rectangle(s = @point, e = mark)
|
|
1272
|
-
check_read_only_flag
|
|
1273
|
-
apply_on_rectangle(s, e, reverse: true) do |start_col, end_col, col, line_start|
|
|
1274
|
-
start_pos = @point
|
|
1275
|
-
if col < start_col
|
|
1276
|
-
insert(" " * (end_col - start_col))
|
|
1277
|
-
else
|
|
1278
|
-
while col < end_col && !end_of_line?
|
|
1279
|
-
forward_char
|
|
1280
|
-
col = display_width(substring(line_start, @point))
|
|
1281
|
-
end
|
|
1282
|
-
end_pos = @point
|
|
1283
|
-
|
|
1284
|
-
delete_region(start_pos, end_pos) if end_pos > start_pos
|
|
1285
|
-
insert(" " * (end_col - start_col))
|
|
1286
|
-
end
|
|
1287
|
-
end
|
|
1288
|
-
end
|
|
1289
|
-
|
|
1290
1110
|
def undo
|
|
1291
1111
|
undo_or_redo(:undo, @undo_stack, @redo_stack)
|
|
1292
1112
|
end
|
|
@@ -1295,7 +1115,7 @@ module Textbringer
|
|
|
1295
1115
|
undo_or_redo(:redo, @redo_stack, @undo_stack)
|
|
1296
1116
|
end
|
|
1297
1117
|
|
|
1298
|
-
def re_search_forward(s, raise_error: true, count: 1)
|
|
1118
|
+
def re_search_forward(s, raise_error: true, goto_beginning: false, count: 1)
|
|
1299
1119
|
if count < 0
|
|
1300
1120
|
return re_search_backward(s, raise_error: raise_error, count: -count)
|
|
1301
1121
|
end
|
|
@@ -1310,7 +1130,7 @@ module Textbringer
|
|
|
1310
1130
|
return nil
|
|
1311
1131
|
end
|
|
1312
1132
|
end
|
|
1313
|
-
pos = match_end(0)
|
|
1133
|
+
pos = goto_beginning ? match_beginning(0) : match_end(0)
|
|
1314
1134
|
end
|
|
1315
1135
|
goto_char(pos)
|
|
1316
1136
|
end
|
|
@@ -1356,6 +1176,7 @@ module Textbringer
|
|
|
1356
1176
|
|
|
1357
1177
|
def byteindex(forward, re, pos)
|
|
1358
1178
|
@match_offsets = []
|
|
1179
|
+
@last_match = nil
|
|
1359
1180
|
method = forward ? :byteindex : :byterindex
|
|
1360
1181
|
adjust_gap(0, 0)
|
|
1361
1182
|
s = @contents.byteslice(@gap_end..-1)
|
|
@@ -1364,9 +1185,9 @@ module Textbringer
|
|
|
1364
1185
|
end
|
|
1365
1186
|
i = s.send(method, re, pos)
|
|
1366
1187
|
if i
|
|
1367
|
-
|
|
1368
|
-
(0 ..
|
|
1369
|
-
@match_offsets.push(
|
|
1188
|
+
@last_match = Regexp.last_match
|
|
1189
|
+
(0 .. @last_match.size - 1).each do |j|
|
|
1190
|
+
@match_offsets.push(@last_match.byteoffset(j))
|
|
1370
1191
|
end
|
|
1371
1192
|
i
|
|
1372
1193
|
else
|
|
@@ -1491,6 +1312,10 @@ module Textbringer
|
|
|
1491
1312
|
end
|
|
1492
1313
|
end
|
|
1493
1314
|
|
|
1315
|
+
def minor_mode_active?(mode_class)
|
|
1316
|
+
@minor_modes.any? { |mode| mode.instance_of?(mode_class) }
|
|
1317
|
+
end
|
|
1318
|
+
|
|
1494
1319
|
def mode_names
|
|
1495
1320
|
names = []
|
|
1496
1321
|
names.push(mode&.name || 'None')
|
|
@@ -1948,7 +1773,6 @@ module Textbringer
|
|
|
1948
1773
|
end
|
|
1949
1774
|
|
|
1950
1775
|
KILL_RING = Ring.new
|
|
1951
|
-
@@killed_rectangle = nil
|
|
1952
1776
|
|
|
1953
1777
|
class UndoableAction
|
|
1954
1778
|
attr_accessor :version
|
|
@@ -93,7 +93,12 @@ module Textbringer
|
|
|
93
93
|
|
|
94
94
|
define_command(:exchange_point_and_mark,
|
|
95
95
|
doc: "Exchange the positions of point and mark.") do
|
|
96
|
-
Buffer.current
|
|
96
|
+
buffer = Buffer.current
|
|
97
|
+
buffer.exchange_point_and_mark
|
|
98
|
+
# Activate mark if transient mark mode is enabled
|
|
99
|
+
if TransientMarkMode.enabled?
|
|
100
|
+
buffer.activate_mark
|
|
101
|
+
end
|
|
97
102
|
end
|
|
98
103
|
|
|
99
104
|
define_command(:copy_region,
|
|
@@ -129,36 +134,6 @@ module Textbringer
|
|
|
129
134
|
Buffer.current.delete_region
|
|
130
135
|
end
|
|
131
136
|
|
|
132
|
-
define_command(:kill_rectangle,
|
|
133
|
-
doc: "Kill the text of the region-rectangle, saving its contents as the last killed rectangle.") do
|
|
134
|
-
Buffer.current.kill_rectangle
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
define_command(:copy_rectangle_as_kill,
|
|
138
|
-
doc: "Save the text of the region-rectangle as the last killed rectangle.") do
|
|
139
|
-
Buffer.current.copy_rectangle
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
define_command(:delete_rectangle,
|
|
143
|
-
doc: "Delete the text of the region-rectangle.") do
|
|
144
|
-
Buffer.current.delete_rectangle
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
define_command(:yank_rectangle,
|
|
148
|
-
doc: "Yank the last killed rectangle with its upper left corner at point.") do
|
|
149
|
-
Buffer.current.yank_rectangle
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
define_command(:open_rectangle,
|
|
153
|
-
doc: "Insert blank space to fill the space of the region-rectangle. This pushes the previous contents of the region-rectangle to the right.") do
|
|
154
|
-
Buffer.current.open_rectangle
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
define_command(:clear_rectangle,
|
|
158
|
-
doc: "Clear the region-rectangle by replacing its contents with spaces.") do
|
|
159
|
-
Buffer.current.clear_rectangle
|
|
160
|
-
end
|
|
161
|
-
|
|
162
137
|
define_command(:transpose_chars,
|
|
163
138
|
doc: "Transpose characters.") do
|
|
164
139
|
Buffer.current.transpose_chars
|
|
@@ -177,6 +152,10 @@ module Textbringer
|
|
|
177
152
|
buffer.pop_to_mark
|
|
178
153
|
else
|
|
179
154
|
buffer.push_mark
|
|
155
|
+
# Activate mark if transient mark mode is enabled
|
|
156
|
+
if TransientMarkMode.enabled?
|
|
157
|
+
buffer.activate_mark
|
|
158
|
+
end
|
|
180
159
|
message("Mark set")
|
|
181
160
|
end
|
|
182
161
|
end
|
|
@@ -77,7 +77,10 @@ module Textbringer
|
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
def isearch_done
|
|
80
|
-
|
|
80
|
+
# Don't delete visible_mark if mark is active (transient mark mode)
|
|
81
|
+
unless Buffer.current.mark_active?
|
|
82
|
+
Buffer.current.delete_visible_mark
|
|
83
|
+
end
|
|
81
84
|
Controller.current.overriding_map = nil
|
|
82
85
|
remove_hook(:pre_command_hook, :isearch_pre_command_hook)
|
|
83
86
|
ISEARCH_STATUS[:last_string] = ISEARCH_STATUS[:string]
|
|
@@ -154,8 +157,11 @@ module Textbringer
|
|
|
154
157
|
if Buffer.current != Buffer.minibuffer
|
|
155
158
|
message(isearch_prompt + ISEARCH_STATUS[:string], log: false)
|
|
156
159
|
end
|
|
157
|
-
|
|
158
|
-
|
|
160
|
+
# Don't update visible_mark if mark is already active (transient mark mode)
|
|
161
|
+
unless Buffer.current.mark_active?
|
|
162
|
+
Buffer.current.set_visible_mark(forward ? match_beginning(0) :
|
|
163
|
+
match_end(0))
|
|
164
|
+
end
|
|
159
165
|
goto_char(forward ? match_end(0) : match_beginning(0))
|
|
160
166
|
else
|
|
161
167
|
if Buffer.current != Buffer.minibuffer
|