textbringer 23 → 25
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 -0
- data/lib/textbringer/buffer.rb +19 -11
- 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/misc.rb +19 -0
- data/lib/textbringer/commands/tetris.rb +10 -0
- data/lib/textbringer/commands/windows.rb +14 -0
- data/lib/textbringer/config.rb +2 -1
- data/lib/textbringer/face.rb +71 -15
- data/lib/textbringer/faces/gamegrid.rb +23 -0
- data/lib/textbringer/gamegrid.rb +160 -0
- data/lib/textbringer/highlight_context.rb +21 -0
- data/lib/textbringer/keymap.rb +1 -0
- data/lib/textbringer/mode.rb +25 -0
- data/lib/textbringer/modes/dired_mode.rb +322 -0
- data/lib/textbringer/modes/gamegrid_mode.rb +51 -0
- data/lib/textbringer/modes/ruby_mode.rb +298 -181
- data/lib/textbringer/modes/tetris_mode.rb +316 -0
- data/lib/textbringer/theme.rb +180 -0
- data/lib/textbringer/themes/catppuccin.rb +105 -0
- data/lib/textbringer/themes/github.rb +89 -0
- data/lib/textbringer/themes/gruvbox.rb +84 -0
- data/lib/textbringer/themes/molokai.rb +67 -0
- data/lib/textbringer/themes/sonokai.rb +63 -0
- data/lib/textbringer/themes/tokyonight.rb +70 -0
- data/lib/textbringer/version.rb +1 -1
- data/lib/textbringer/window.rb +29 -36
- data/lib/textbringer.rb +9 -0
- data/textbringer.gemspec +1 -0
- metadata +32 -5
- data/lib/textbringer/faces/basic.rb +0 -8
- data/lib/textbringer/faces/completion.rb +0 -4
- data/lib/textbringer/faces/programming.rb +0 -6
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
module Textbringer
|
|
2
|
+
class DiredMode < Mode
|
|
3
|
+
define_keymap :DIRED_MODE_MAP
|
|
4
|
+
DIRED_MODE_MAP.define_key("n", :dired_next_line_command)
|
|
5
|
+
DIRED_MODE_MAP.define_key(" ", :dired_next_line_command)
|
|
6
|
+
DIRED_MODE_MAP.define_key("\C-n", :dired_next_line_command)
|
|
7
|
+
DIRED_MODE_MAP.define_key("p", :dired_previous_line_command)
|
|
8
|
+
DIRED_MODE_MAP.define_key("\C-p", :dired_previous_line_command)
|
|
9
|
+
DIRED_MODE_MAP.define_key("^", :dired_up_directory_command)
|
|
10
|
+
DIRED_MODE_MAP.define_key("\C-m", :dired_find_file_command)
|
|
11
|
+
DIRED_MODE_MAP.define_key("f", :dired_find_file_command)
|
|
12
|
+
DIRED_MODE_MAP.define_key("o", :dired_find_file_other_window_command)
|
|
13
|
+
DIRED_MODE_MAP.define_key("d", :dired_flag_file_deletion_command)
|
|
14
|
+
DIRED_MODE_MAP.define_key("u", :dired_unmark_command)
|
|
15
|
+
DIRED_MODE_MAP.define_key("U", :dired_unmark_all_command)
|
|
16
|
+
DIRED_MODE_MAP.define_key("x", :dired_do_flagged_delete_command)
|
|
17
|
+
DIRED_MODE_MAP.define_key("R", :dired_do_rename_command)
|
|
18
|
+
DIRED_MODE_MAP.define_key("C", :dired_do_copy_command)
|
|
19
|
+
DIRED_MODE_MAP.define_key("+", :dired_create_directory_command)
|
|
20
|
+
DIRED_MODE_MAP.define_key("g", :dired_revert_command)
|
|
21
|
+
DIRED_MODE_MAP.define_key("q", :bury_buffer)
|
|
22
|
+
|
|
23
|
+
# Deletion-flagged lines
|
|
24
|
+
define_syntax :dired_flagged, /^D .+$/
|
|
25
|
+
# Symlinks
|
|
26
|
+
define_syntax :dired_symlink, /^[ D] \S+\s+\d+\s+[\d-]+ [\d:]+ .+ -> .+$/
|
|
27
|
+
# Directories
|
|
28
|
+
define_syntax :dired_directory, /^[ D] d\S+\s+\d+\s+[\d-]+ [\d:]+ .+\/$/
|
|
29
|
+
# Executables
|
|
30
|
+
define_syntax :dired_executable, /^[ D] -[r-][w-]x.+$/
|
|
31
|
+
|
|
32
|
+
PERM_BITS = [
|
|
33
|
+
["r", 0400], ["w", 0200], ["x", 0100],
|
|
34
|
+
["r", 0040], ["w", 0020], ["x", 0010],
|
|
35
|
+
["r", 0004], ["w", 0002], ["x", 0001]
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
def self.format_permissions(stat)
|
|
39
|
+
type = if stat.directory? then "d"
|
|
40
|
+
elsif stat.symlink? then "l"
|
|
41
|
+
elsif stat.pipe? then "p"
|
|
42
|
+
elsif stat.socket? then "s"
|
|
43
|
+
elsif stat.chardev? then "c"
|
|
44
|
+
elsif stat.blockdev? then "b"
|
|
45
|
+
else "-"
|
|
46
|
+
end
|
|
47
|
+
type + PERM_BITS.map { |ch, mask| stat.mode & mask != 0 ? ch : "-" }.join
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.generate_listing(dir)
|
|
51
|
+
entries = []
|
|
52
|
+
Dir.foreach(dir) do |name|
|
|
53
|
+
path = File.join(dir, name)
|
|
54
|
+
begin
|
|
55
|
+
stat = File.lstat(path)
|
|
56
|
+
perms = format_permissions(stat)
|
|
57
|
+
size = stat.size
|
|
58
|
+
mtime = stat.mtime.strftime("%Y-%m-%d %H:%M")
|
|
59
|
+
if stat.symlink?
|
|
60
|
+
begin
|
|
61
|
+
target = File.readlink(path)
|
|
62
|
+
rescue SystemCallError
|
|
63
|
+
target = "?"
|
|
64
|
+
end
|
|
65
|
+
display = "#{name} -> #{target}"
|
|
66
|
+
elsif stat.directory?
|
|
67
|
+
display = "#{name}/"
|
|
68
|
+
else
|
|
69
|
+
display = name
|
|
70
|
+
end
|
|
71
|
+
entries << {
|
|
72
|
+
name: name,
|
|
73
|
+
display: display,
|
|
74
|
+
perms: perms,
|
|
75
|
+
size: size,
|
|
76
|
+
mtime: mtime,
|
|
77
|
+
directory: stat.directory?
|
|
78
|
+
}
|
|
79
|
+
rescue SystemCallError => e
|
|
80
|
+
entries << {
|
|
81
|
+
name: name,
|
|
82
|
+
display: name,
|
|
83
|
+
perms: "??????????",
|
|
84
|
+
size: 0,
|
|
85
|
+
mtime: "????-??-?? ??:??",
|
|
86
|
+
directory: false,
|
|
87
|
+
error: e.message
|
|
88
|
+
}
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
entries.sort_by! { |e| [e[:directory] ? 0 : 1, e[:name].downcase] }
|
|
93
|
+
|
|
94
|
+
lines = [" #{dir}:\n"]
|
|
95
|
+
entries.each do |e|
|
|
96
|
+
line = " #{e[:perms]} #{e[:size].to_s.rjust(8)} #{e[:mtime]} #{e[:display]}\n"
|
|
97
|
+
lines << line
|
|
98
|
+
end
|
|
99
|
+
lines.join
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def initialize(buffer)
|
|
103
|
+
super(buffer)
|
|
104
|
+
buffer.keymap = DIRED_MODE_MAP
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
define_local_command(:dired_move_to_filename,
|
|
108
|
+
doc: "Move point to the filename on the current line.") do
|
|
109
|
+
@buffer.beginning_of_line
|
|
110
|
+
if @buffer.looking_at?(/^[D ] \S+\s+\d+\s+[\d-]+\s+[\d:]+\s+/)
|
|
111
|
+
@buffer.forward_char(@buffer.match_string(0).length)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
define_local_command(:dired_next_line, doc: "Move to next file line.") do
|
|
116
|
+
@buffer.next_line
|
|
117
|
+
dired_move_to_filename
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
define_local_command(:dired_previous_line, doc: "Move to previous file line.") do
|
|
121
|
+
first_file_line = @buffer.save_excursion {
|
|
122
|
+
@buffer.beginning_of_buffer
|
|
123
|
+
@buffer.current_line
|
|
124
|
+
}
|
|
125
|
+
if @buffer.current_line > first_file_line
|
|
126
|
+
@buffer.previous_line
|
|
127
|
+
end
|
|
128
|
+
dired_move_to_filename
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
define_local_command(:dired_up_directory, doc: "Go up to parent directory.") do
|
|
132
|
+
dir = @buffer[:dired_directory]
|
|
133
|
+
parent = File.dirname(dir)
|
|
134
|
+
dired(parent)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
define_local_command(:dired_find_file, doc: "Visit file or directory at point.") do
|
|
138
|
+
name = current_file_name
|
|
139
|
+
return unless name
|
|
140
|
+
dir = @buffer[:dired_directory]
|
|
141
|
+
path = File.join(dir, name)
|
|
142
|
+
if File.directory?(path)
|
|
143
|
+
dired(path)
|
|
144
|
+
else
|
|
145
|
+
find_file(path)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
define_local_command(:dired_find_file_other_window,
|
|
150
|
+
doc: "Visit file at point in other window.") do
|
|
151
|
+
name = current_file_name
|
|
152
|
+
return unless name
|
|
153
|
+
dir = @buffer[:dired_directory]
|
|
154
|
+
path = File.join(dir, name)
|
|
155
|
+
if Window.list.size == 1
|
|
156
|
+
split_window
|
|
157
|
+
end
|
|
158
|
+
other_window
|
|
159
|
+
if File.directory?(path)
|
|
160
|
+
dired(path)
|
|
161
|
+
else
|
|
162
|
+
find_file(path)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
define_local_command(:dired_flag_file_deletion,
|
|
167
|
+
doc: "Flag file at point for deletion.") do
|
|
168
|
+
set_flag("D")
|
|
169
|
+
dired_next_line
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
define_local_command(:dired_unmark, doc: "Remove deletion flag from file at point.") do
|
|
173
|
+
set_flag(" ")
|
|
174
|
+
dired_next_line
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
define_local_command(:dired_unmark_all, doc: "Remove all deletion flags.") do
|
|
178
|
+
@buffer.save_excursion do
|
|
179
|
+
@buffer.beginning_of_buffer
|
|
180
|
+
while !@buffer.end_of_buffer?
|
|
181
|
+
set_flag(" ")
|
|
182
|
+
@buffer.next_line
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
define_local_command(:dired_do_flagged_delete,
|
|
188
|
+
doc: "Delete files flagged for deletion.") do
|
|
189
|
+
files = collect_flagged_files
|
|
190
|
+
return if files.empty?
|
|
191
|
+
list = files.map { |f| " #{f}" }.join("\n")
|
|
192
|
+
if yes_or_no?("Delete these files?")
|
|
193
|
+
files.each do |name|
|
|
194
|
+
next if name == "." || name == ".."
|
|
195
|
+
path = File.join(@buffer[:dired_directory], name)
|
|
196
|
+
begin
|
|
197
|
+
if File.directory?(path) && !File.symlink?(path)
|
|
198
|
+
FileUtils.rm_rf(path)
|
|
199
|
+
else
|
|
200
|
+
File.delete(path)
|
|
201
|
+
end
|
|
202
|
+
rescue SystemCallError => e
|
|
203
|
+
message("Error deleting #{name}: #{e.message}")
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
dired_revert
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
define_local_command(:dired_do_rename, doc: "Rename/move file at point.") do
|
|
211
|
+
name = current_file_name
|
|
212
|
+
return unless name
|
|
213
|
+
dir = @buffer[:dired_directory]
|
|
214
|
+
src = File.join(dir, name)
|
|
215
|
+
dest = read_file_name("Rename #{name} to: ", default: dir + "/")
|
|
216
|
+
dest = File.expand_path(dest, dir)
|
|
217
|
+
FileUtils.mv(src, dest)
|
|
218
|
+
dired_revert
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
define_local_command(:dired_do_copy, doc: "Copy file at point.") do
|
|
222
|
+
name = current_file_name
|
|
223
|
+
return unless name
|
|
224
|
+
dir = @buffer[:dired_directory]
|
|
225
|
+
src = File.join(dir, name)
|
|
226
|
+
dest = read_file_name("Copy #{name} to: ", default: dir + "/")
|
|
227
|
+
dest = File.expand_path(dest, dir)
|
|
228
|
+
FileUtils.cp_r(src, dest)
|
|
229
|
+
dired_revert
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
define_local_command(:dired_create_directory, doc: "Create a new directory.") do
|
|
233
|
+
dir = @buffer[:dired_directory]
|
|
234
|
+
name = read_from_minibuffer("Create directory: ", default: dir + "/")
|
|
235
|
+
name = File.expand_path(name, dir)
|
|
236
|
+
FileUtils.mkdir_p(name)
|
|
237
|
+
dired_revert
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
define_local_command(:dired_revert, doc: "Refresh directory listing.") do
|
|
241
|
+
dir = @buffer[:dired_directory]
|
|
242
|
+
saved_name = current_file_name
|
|
243
|
+
saved_line = @buffer.current_line
|
|
244
|
+
@buffer.read_only_edit do
|
|
245
|
+
@buffer.clear
|
|
246
|
+
@buffer.insert(DiredMode.generate_listing(dir))
|
|
247
|
+
@buffer.beginning_of_buffer
|
|
248
|
+
@buffer.forward_line
|
|
249
|
+
end
|
|
250
|
+
if saved_name
|
|
251
|
+
@buffer.beginning_of_buffer
|
|
252
|
+
found = false
|
|
253
|
+
while !@buffer.end_of_buffer?
|
|
254
|
+
if current_file_name == saved_name
|
|
255
|
+
found = true
|
|
256
|
+
break
|
|
257
|
+
end
|
|
258
|
+
@buffer.next_line
|
|
259
|
+
end
|
|
260
|
+
unless found
|
|
261
|
+
goto_line(saved_line)
|
|
262
|
+
@buffer.beginning_of_line
|
|
263
|
+
if @buffer.end_of_buffer? || current_file_name.nil?
|
|
264
|
+
@buffer.end_of_buffer
|
|
265
|
+
@buffer.previous_line while current_file_name.nil? && @buffer.current_line > 2
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
dired_move_to_filename
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
private
|
|
273
|
+
|
|
274
|
+
def current_file_name
|
|
275
|
+
@buffer.save_excursion do
|
|
276
|
+
@buffer.beginning_of_line
|
|
277
|
+
# Line format: " perms size date time display_name"
|
|
278
|
+
# or: "D perms size date time display_name"
|
|
279
|
+
if @buffer.looking_at?(/^[D ] (\S+)\s+\d+\s+[\d-]+\s+[\d:]+\s+(.+)$/)
|
|
280
|
+
perms = @buffer.match_string(1)
|
|
281
|
+
display = @buffer.match_string(2)
|
|
282
|
+
# Strip symlink target: "name -> target" -> "name" (only for symlinks)
|
|
283
|
+
display = display.sub(/ -> .+$/, "") if perms.start_with?("l")
|
|
284
|
+
# Strip trailing slash for directories: "name/" -> "name"
|
|
285
|
+
display = display.chomp("/")
|
|
286
|
+
display
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def set_flag(char)
|
|
292
|
+
@buffer.read_only_edit do
|
|
293
|
+
@buffer.save_excursion do
|
|
294
|
+
@buffer.beginning_of_line
|
|
295
|
+
if @buffer.looking_at?(/^[D ]/)
|
|
296
|
+
@buffer.delete_char(1)
|
|
297
|
+
@buffer.insert(char)
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def collect_flagged_files
|
|
304
|
+
files = []
|
|
305
|
+
@buffer.save_excursion do
|
|
306
|
+
@buffer.beginning_of_buffer
|
|
307
|
+
while !@buffer.end_of_buffer?
|
|
308
|
+
@buffer.beginning_of_line
|
|
309
|
+
if @buffer.looking_at?(/^D (\S+)\s+\d+\s+[\d-]+\s+[\d:]+\s+(.+)$/)
|
|
310
|
+
perms = @buffer.match_string(1)
|
|
311
|
+
display = @buffer.match_string(2)
|
|
312
|
+
display = display.sub(/ -> .+$/, "") if perms.start_with?("l")
|
|
313
|
+
display = display.chomp("/")
|
|
314
|
+
files << display
|
|
315
|
+
end
|
|
316
|
+
@buffer.next_line
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
files
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Textbringer
|
|
2
|
+
class GamegridMode < Mode
|
|
3
|
+
@syntax_table = {}
|
|
4
|
+
|
|
5
|
+
def self.inherited(child)
|
|
6
|
+
super
|
|
7
|
+
child.instance_variable_set(:@syntax_table, {})
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
define_keymap :GAMEGRID_MODE_MAP
|
|
11
|
+
GAMEGRID_MODE_MAP.define_key("q", :gamegrid_quit_command)
|
|
12
|
+
|
|
13
|
+
def initialize(buffer)
|
|
14
|
+
super(buffer)
|
|
15
|
+
buffer.keymap = GAMEGRID_MODE_MAP
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
define_local_command(:gamegrid_init,
|
|
20
|
+
doc: "Initialize a gamegrid in the current buffer.") do |width, height, margin_left: 0|
|
|
21
|
+
grid = Gamegrid.new(width, height, margin_left: margin_left)
|
|
22
|
+
@buffer[:gamegrid] = grid
|
|
23
|
+
@buffer.read_only = true
|
|
24
|
+
grid
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def highlight(ctx)
|
|
28
|
+
grid = @buffer[:gamegrid]
|
|
29
|
+
return unless grid
|
|
30
|
+
grid.apply_highlights(ctx)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
define_local_command(:gamegrid_refresh,
|
|
34
|
+
doc: "Refresh the gamegrid display.") do
|
|
35
|
+
grid = @buffer[:gamegrid]
|
|
36
|
+
return unless grid
|
|
37
|
+
@buffer.read_only_edit do
|
|
38
|
+
@buffer.clear
|
|
39
|
+
@buffer.insert(grid.render)
|
|
40
|
+
@buffer.beginning_of_buffer
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
define_local_command(:gamegrid_quit,
|
|
45
|
+
doc: "Quit the current game.") do
|
|
46
|
+
grid = @buffer[:gamegrid]
|
|
47
|
+
grid&.stop_timer
|
|
48
|
+
bury_buffer
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|