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.
@@ -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