diakonos 0.8.8 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +433 -0
  3. data/LICENCE.md +675 -0
  4. data/bin/diakonos +6 -0
  5. data/diakonos-256-colour.conf +220 -0
  6. data/diakonos.conf +1802 -0
  7. data/help/about-help.dhf +31 -0
  8. data/help/clipboard.dhf +45 -0
  9. data/help/close-file.dhf +6 -0
  10. data/help/code-block-navigation.dhf +16 -0
  11. data/help/column-markers.dhf +15 -0
  12. data/help/config.dhf +69 -0
  13. data/help/cursor-stack.dhf +19 -0
  14. data/help/delete.dhf +41 -0
  15. data/help/extensions.dhf +125 -0
  16. data/help/file-type.dhf +24 -0
  17. data/help/key-mapping.dhf +127 -0
  18. data/help/line-numbers.dhf +22 -0
  19. data/help/macros.dhf +27 -0
  20. data/help/new-file.dhf +6 -0
  21. data/help/open-file.dhf +21 -0
  22. data/help/quit.dhf +7 -0
  23. data/help/resizing.dhf +19 -0
  24. data/help/ruby.dhf +17 -0
  25. data/help/save-file.dhf +10 -0
  26. data/help/scripting.dhf +92 -0
  27. data/help/search.dhf +103 -0
  28. data/help/shell.dhf +60 -0
  29. data/help/speed.dhf +23 -0
  30. data/help/support.dhf +15 -0
  31. data/help/switch-buffers.dhf +15 -0
  32. data/help/tabs.dhf +36 -0
  33. data/help/undo.dhf +9 -0
  34. data/help/uninstall.dhf +18 -0
  35. data/help/welcome.dhf +32 -0
  36. data/help/word-wrap.dhf +17 -0
  37. data/lib/diakonos/about.rb +69 -0
  38. data/lib/diakonos/bookmark.rb +46 -0
  39. data/lib/diakonos/buffer/bookmarking.rb +47 -0
  40. data/lib/diakonos/buffer/cursor.rb +335 -0
  41. data/lib/diakonos/buffer/delete.rb +170 -0
  42. data/lib/diakonos/buffer/display.rb +356 -0
  43. data/lib/diakonos/buffer/file.rb +157 -0
  44. data/lib/diakonos/buffer/indentation.rb +175 -0
  45. data/lib/diakonos/buffer/searching.rb +552 -0
  46. data/lib/diakonos/buffer/selection.rb +360 -0
  47. data/lib/diakonos/buffer/undo.rb +73 -0
  48. data/lib/diakonos/buffer-hash.rb +60 -0
  49. data/lib/diakonos/buffer-management.rb +59 -0
  50. data/lib/diakonos/buffer.rb +698 -0
  51. data/lib/diakonos/clipboard-klipper-dbus.rb +62 -0
  52. data/lib/diakonos/clipboard-klipper.rb +62 -0
  53. data/lib/diakonos/clipboard-osx.rb +59 -0
  54. data/lib/diakonos/clipboard-xclip.rb +60 -0
  55. data/lib/diakonos/clipboard.rb +47 -0
  56. data/lib/diakonos/config-file.rb +67 -0
  57. data/lib/diakonos/config.rb +381 -0
  58. data/lib/diakonos/core-ext/enumerable.rb +15 -0
  59. data/lib/diakonos/core-ext/hash.rb +60 -0
  60. data/lib/diakonos/core-ext/object.rb +6 -0
  61. data/lib/diakonos/core-ext/regexp.rb +6 -0
  62. data/lib/diakonos/core-ext/string.rb +122 -0
  63. data/lib/diakonos/ctag.rb +28 -0
  64. data/lib/diakonos/cursor.rb +27 -0
  65. data/lib/diakonos/display/format.rb +75 -0
  66. data/lib/diakonos/display.rb +336 -0
  67. data/lib/diakonos/extension-set.rb +49 -0
  68. data/lib/diakonos/extension.rb +34 -0
  69. data/lib/diakonos/finding.rb +40 -0
  70. data/lib/diakonos/functions/basics.rb +34 -0
  71. data/lib/diakonos/functions/bookmarking.rb +61 -0
  72. data/lib/diakonos/functions/buffers.rb +489 -0
  73. data/lib/diakonos/functions/clipboard.rb +70 -0
  74. data/lib/diakonos/functions/cursor.rb +264 -0
  75. data/lib/diakonos/functions/grepping.rb +83 -0
  76. data/lib/diakonos/functions/indentation.rb +71 -0
  77. data/lib/diakonos/functions/readline.rb +93 -0
  78. data/lib/diakonos/functions/search.rb +179 -0
  79. data/lib/diakonos/functions/selection.rb +98 -0
  80. data/lib/diakonos/functions/sessions.rb +78 -0
  81. data/lib/diakonos/functions/shell.rb +250 -0
  82. data/lib/diakonos/functions/tags.rb +65 -0
  83. data/lib/diakonos/functions/text-manipulation.rb +196 -0
  84. data/lib/diakonos/functions-deprecated.rb +77 -0
  85. data/lib/diakonos/functions.rb +292 -0
  86. data/lib/diakonos/grep.rb +98 -0
  87. data/lib/diakonos/help.rb +47 -0
  88. data/lib/diakonos/hooks.rb +13 -0
  89. data/lib/diakonos/installation.rb +19 -0
  90. data/lib/diakonos/interaction-handler.rb +216 -0
  91. data/lib/diakonos/interaction.rb +52 -0
  92. data/lib/diakonos/key-map.rb +62 -0
  93. data/lib/diakonos/keying.rb +442 -0
  94. data/lib/diakonos/line-mover.rb +42 -0
  95. data/lib/diakonos/list.rb +59 -0
  96. data/lib/diakonos/logging.rb +27 -0
  97. data/lib/diakonos/mode.rb +17 -0
  98. data/lib/diakonos/mouse.rb +18 -0
  99. data/lib/diakonos/number-fitter.rb +11 -0
  100. data/lib/diakonos/range.rb +31 -0
  101. data/lib/diakonos/readline/functions.rb +82 -0
  102. data/lib/diakonos/readline.rb +222 -0
  103. data/lib/diakonos/search.rb +58 -0
  104. data/lib/diakonos/sessions.rb +257 -0
  105. data/lib/diakonos/sized-array.rb +48 -0
  106. data/lib/diakonos/text-mark.rb +19 -0
  107. data/lib/diakonos/vendor/fuzzy_file_finder.rb +365 -0
  108. data/lib/diakonos/version.rb +25 -0
  109. data/lib/diakonos/window.rb +43 -0
  110. data/lib/diakonos.rb +592 -0
  111. metadata +160 -66
@@ -0,0 +1,365 @@
1
+ #--
2
+ # ==================================================================
3
+ # Author: Jamis Buck (jamis@jamisbuck.org)
4
+ # Date: 2008-10-09
5
+ #
6
+ # This file is in the public domain. Usage, modification, and
7
+ # redistribution of this file are unrestricted.
8
+ # ==================================================================
9
+ #++
10
+
11
+ # The "fuzzy" file finder provides a way for searching a directory
12
+ # tree with only a partial name. This is similar to the "cmd-T"
13
+ # feature in TextMate (http://macromates.com).
14
+ #
15
+ # Usage:
16
+ #
17
+ # finder = FuzzyFileFinder.new
18
+ # finder.search("app/blogcon") do |match|
19
+ # puts match[:highlighted_path]
20
+ # end
21
+ #
22
+ # In the above example, all files matching "app/blogcon" will be
23
+ # yielded to the block. The given pattern is reduced to a regular
24
+ # expression internally, so that any file that contains those
25
+ # characters in that order (even if there are other characters
26
+ # in between) will match.
27
+ #
28
+ # In other words, "app/blogcon" would match any of the following
29
+ # (parenthesized strings indicate how the match was made):
30
+ #
31
+ # * (app)/controllers/(blog)_(con)troller.rb
32
+ # * lib/c(ap)_(p)ool/(bl)ue_(o)r_(g)reen_(co)loratio(n)
33
+ # * test/(app)/(blog)_(con)troller_test.rb
34
+ #
35
+ # And so forth.
36
+ class FuzzyFileFinder
37
+ # This is the exception that is raised if you try to scan a
38
+ # directory tree with too many entries. By default, a ceiling of
39
+ # 10,000 entries is enforced, but you can change that number via
40
+ # the +ceiling+ parameter to FuzzyFileFinder.new.
41
+ class TooManyEntries < RuntimeError; end
42
+
43
+ # Used internally to represent a run of characters within a
44
+ # match. This is used to build the highlighted version of
45
+ # a file name.
46
+ class CharacterRun < Struct.new(:string, :inside) #:nodoc:
47
+ def to_s
48
+ if inside
49
+ "(#{string})"
50
+ else
51
+ string
52
+ end
53
+ end
54
+ end
55
+
56
+ # Used internally to represent a file within the directory tree.
57
+ class FileSystemEntry #:nodoc:
58
+ attr_reader :parent
59
+ attr_reader :name
60
+
61
+ def initialize(parent, name)
62
+ @parent = parent
63
+ @name = name
64
+ end
65
+
66
+ def path
67
+ File.join(parent.name, name)
68
+ end
69
+ end
70
+
71
+ # Used internally to represent a subdirectory within the directory
72
+ # tree.
73
+ class Directory #:nodoc:
74
+ attr_reader :name
75
+
76
+ def initialize(name, is_root=false)
77
+ @name = name
78
+ @is_root = is_root
79
+ end
80
+
81
+ def root?
82
+ is_root
83
+ end
84
+ end
85
+
86
+ # The roots directory trees to search.
87
+ attr_reader :roots
88
+
89
+ # The list of files beneath all +roots+
90
+ attr_reader :files
91
+
92
+ # The maximum number of files beneath all +roots+
93
+ attr_reader :ceiling
94
+
95
+ # The prefix shared by all +roots+.
96
+ attr_reader :shared_prefix
97
+
98
+ # The list of glob patterns to ignore.
99
+ attr_reader :ignores
100
+
101
+ # Initializes a new FuzzyFileFinder. This will scan the
102
+ # given +directories+, using +ceiling+ as the maximum number
103
+ # of entries to scan. If there are more than +ceiling+ entries
104
+ # a TooManyEntries exception will be raised.
105
+ def initialize( params = {} )
106
+ @ceiling = params[:ceiling] || 10_000
107
+ @ignores = Array(params[:ignores])
108
+
109
+ if params[:directories]
110
+ directories = Array(params[:directories])
111
+ directories << "." if directories.empty?
112
+ else
113
+ directories = ['.']
114
+ end
115
+
116
+ @recursive = params[:recursive].nil? ? true : params[:recursive]
117
+
118
+ # expand any paths with ~
119
+ root_dirnames = directories.map { |d|
120
+ File.realpath(d)
121
+ }.select { |d|
122
+ File.directory?(d)
123
+ }.uniq
124
+
125
+ @roots = root_dirnames.map { |d| Directory.new(d, true) }
126
+ @shared_prefix = determine_shared_prefix
127
+ @shared_prefix_re = Regexp.new("^#{Regexp.escape(shared_prefix)}" + (shared_prefix.empty? ? "" : "/"))
128
+
129
+ @files = []
130
+ @directories = {} # To detect link cycles
131
+
132
+ rescan!
133
+ end
134
+
135
+ # Rescans the subtree. If the directory contents every change,
136
+ # you'll need to call this to force the finder to be aware of
137
+ # the changes.
138
+ def rescan!
139
+ @files.clear
140
+ roots.each { |root| follow_tree(root) }
141
+ end
142
+
143
+ # Takes the given +pattern+ (which must be a string) and searches
144
+ # all files beneath +root+, yielding each match.
145
+ #
146
+ # +pattern+ is interpreted thus:
147
+ #
148
+ # * "foo" : look for any file with the characters 'f', 'o', and 'o'
149
+ # in its basename (discounting directory names). The characters
150
+ # must be in that order.
151
+ # * "foo/bar" : look for any file with the characters 'b', 'a',
152
+ # and 'r' in its basename (discounting directory names). Also,
153
+ # any successful match must also have at least one directory
154
+ # element matching the characters 'f', 'o', and 'o' (in that
155
+ # order.
156
+ # * "foo/bar/baz" : same as "foo/bar", but matching two
157
+ # directory elements in addition to a file name of "baz".
158
+ #
159
+ # Each yielded match will be a hash containing the following keys:
160
+ #
161
+ # * :path refers to the full path to the file
162
+ # * :directory refers to the directory of the file
163
+ # * :name refers to the name of the file (without directory)
164
+ # * :highlighted_directory refers to the directory of the file with
165
+ # matches highlighted in parentheses.
166
+ # * :highlighted_name refers to the name of the file with matches
167
+ # highlighted in parentheses
168
+ # * :highlighted_path refers to the full path of the file with
169
+ # matches highlighted in parentheses
170
+ # * :abbr refers to an abbreviated form of :highlighted_path, where
171
+ # path segments without matches are compressed to just their first
172
+ # character.
173
+ # * :score refers to a value between 0 and 1 indicating how closely
174
+ # the file matches the given pattern. A score of 1 means the
175
+ # pattern matches the file exactly.
176
+ def search(pattern, &block)
177
+ path_parts = pattern.strip.split("/")
178
+ path_parts.push "" if pattern[-1,1] == "/"
179
+
180
+ file_name_part = path_parts.pop || ""
181
+
182
+ if path_parts.any?
183
+ path_regex_raw = "^(.*?)" + path_parts.map { |part| make_pattern(part) }.join("(.*?/.*?)") + "(.*?)$"
184
+ path_regex = Regexp.new(path_regex_raw, Regexp::IGNORECASE)
185
+ end
186
+
187
+ file_regex_raw = "^(.*?)" << make_pattern(file_name_part) << "(.*)$"
188
+ file_regex = Regexp.new(file_regex_raw, Regexp::IGNORECASE)
189
+
190
+ path_matches = {}
191
+ files.each do |file|
192
+ path_match = match_path(file.parent, path_matches, path_regex, path_parts.length)
193
+ next if path_match[:missed]
194
+
195
+ match_file(file, file_regex, path_match, &block)
196
+ end
197
+ end
198
+
199
+ # Takes the given +pattern+ (which must be a string, formatted as
200
+ # described in #search), and returns up to +max+ matches in an
201
+ # Array. If +max+ is nil, all matches will be returned.
202
+ def find(pattern, max=nil)
203
+ results = []
204
+ search(pattern) do |match|
205
+ results << match
206
+ break if max && results.length >= max
207
+ end
208
+ return results
209
+ end
210
+
211
+ # Displays the finder object in a sane, non-explosive manner.
212
+ def inspect #:nodoc:
213
+ "#<%s:0x%x roots=%s, files=%d>" % [self.class.name, object_id, roots.map { |r| r.name.inspect }.join(", "), files.length]
214
+ end
215
+
216
+ private
217
+
218
+ # Recursively scans +directory+ and all files and subdirectories
219
+ # beneath it, depth-first.
220
+ def follow_tree(directory)
221
+ real_dir = File.realpath(directory.name)
222
+ if ! @directories[real_dir]
223
+ @directories[real_dir] = true
224
+
225
+ Dir.entries(directory.name).each do |entry|
226
+ next if entry[0,1] == "."
227
+ raise TooManyEntries if files.length > ceiling
228
+
229
+ full = File.join(directory.name, entry)
230
+ next if ignore?(full)
231
+
232
+ if File.directory?(full)
233
+ if @recursive
234
+ follow_tree(Directory.new(full))
235
+ end
236
+ else
237
+ files.push(FileSystemEntry.new(directory, entry))
238
+ end
239
+ end
240
+ end
241
+ end
242
+
243
+ # Returns +true+ if the given name matches any of the ignore
244
+ # patterns.
245
+ def ignore?(name)
246
+ n = name.sub(@shared_prefix_re, "")
247
+ ignores.any? { |pattern| File.fnmatch(pattern, n) }
248
+ end
249
+
250
+ # Takes the given pattern string "foo" and converts it to a new
251
+ # string "(f)([^/]*?)(o)([^/]*?)(o)" that can be used to create
252
+ # a regular expression.
253
+ def make_pattern(pattern)
254
+ pattern = pattern.split(//)
255
+ pattern << "" if pattern.empty?
256
+
257
+ pattern.inject("") do |regex, character|
258
+ regex << "([^/]*?)" if regex.length > 0
259
+ regex << "(" << Regexp.escape(character) << ")"
260
+ end
261
+ end
262
+
263
+ # Given a MatchData object +match+ and a number of "inside"
264
+ # segments to support, compute both the match score and the
265
+ # highlighted match string. The "inside segments" refers to how
266
+ # many patterns were matched in this one match. For a file name,
267
+ # this will always be one. For directories, it will be one for
268
+ # each directory segment in the original pattern.
269
+ def build_match_result(match, inside_segments)
270
+ runs = []
271
+ inside_chars = total_chars = 0
272
+ match.captures.each_with_index do |capture, index|
273
+ if capture.length > 0
274
+ # odd-numbered captures are matches inside the pattern.
275
+ # even-numbered captures are matches between the pattern's elements.
276
+ inside = index % 2 != 0
277
+
278
+ total_chars += capture.gsub(%r(/), "").length # ignore '/' delimiters
279
+ inside_chars += capture.length if inside
280
+
281
+ if runs.last && runs.last.inside == inside
282
+ runs.last.string << capture
283
+ else
284
+ runs << CharacterRun.new(capture, inside)
285
+ end
286
+ end
287
+ end
288
+
289
+ # Determine the score of this match.
290
+ # 1. fewer "inside runs" (runs corresponding to the original pattern)
291
+ # is better.
292
+ # 2. better coverage of the actual path name is better
293
+
294
+ inside_runs = runs.select { |r| r.inside }
295
+ run_ratio = inside_runs.length.zero? ? 1 : inside_segments / inside_runs.length.to_f
296
+
297
+ char_ratio = total_chars.zero? ? 1 : inside_chars.to_f / total_chars
298
+
299
+ score = run_ratio * char_ratio
300
+
301
+ return { :score => score, :result => runs.join }
302
+ end
303
+
304
+ # Match the given path against the regex, caching the result in +path_matches+.
305
+ # If +path+ is already cached in the path_matches cache, just return the cached
306
+ # value.
307
+ def match_path(path, path_matches, path_regex, path_segments)
308
+ return path_matches[path] if path_matches.key?(path)
309
+
310
+ name_with_slash = path.name + "/" # add a trailing slash for matching the prefix
311
+ matchable_name = name_with_slash.sub(@shared_prefix_re, "")
312
+ matchable_name.chop! # kill the trailing slash
313
+
314
+ if path_regex
315
+ match = matchable_name.match(path_regex)
316
+
317
+ path_matches[path] =
318
+ match && build_match_result(match, path_segments) ||
319
+ { :score => 1, :result => matchable_name, :missed => true }
320
+ else
321
+ path_matches[path] = { :score => 1, :result => matchable_name }
322
+ end
323
+ end
324
+
325
+ # Match +file+ against +file_regex+. If it matches, yield the match
326
+ # metadata to the block.
327
+ def match_file(file, file_regex, path_match, &block)
328
+ if file_match = file.name.match(file_regex)
329
+ match_result = build_match_result(file_match, 1)
330
+ full_match_result = path_match[:result].empty? ? match_result[:result] : File.join(path_match[:result], match_result[:result])
331
+ shortened_path = path_match[:result].gsub(/[^\/]+/) { |m| m.index("(") ? m : m[0,1] }
332
+ abbr = shortened_path.empty? ? match_result[:result] : File.join(shortened_path, match_result[:result])
333
+
334
+ result = { :path => file.path,
335
+ :abbr => abbr,
336
+ :directory => file.parent.name,
337
+ :name => file.name,
338
+ :highlighted_directory => path_match[:result],
339
+ :highlighted_name => match_result[:result],
340
+ :highlighted_path => full_match_result,
341
+ :score => path_match[:score] * match_result[:score] }
342
+ yield result
343
+ end
344
+ end
345
+
346
+ def determine_shared_prefix
347
+ # the common case: if there is only a single root, then the entire
348
+ # name of the root is the shared prefix.
349
+ return roots.first.name if roots.length == 1
350
+
351
+ split_roots = roots.map { |root| root.name.split(%r{/}) }
352
+ segments = split_roots.map { |root| root.length }.max
353
+ master = split_roots.pop
354
+
355
+ segments.times do |segment|
356
+ if !split_roots.all? { |root| root[segment] == master[segment] }
357
+ return master[0,segment].join("/")
358
+ end
359
+ end
360
+
361
+ # shouldn't ever get here, since we uniq the root list before
362
+ # calling this method, but if we do, somehow...
363
+ return roots.first.name
364
+ end
365
+ end
@@ -0,0 +1,25 @@
1
+ module Diakonos
2
+ VERSION = '0.9.8'
3
+ LAST_MODIFIED = 'September 20, 2020'
4
+
5
+ def self.parse_version( s )
6
+ if s
7
+ s.split( '.' ).map { |part| part.to_i }.extend( Comparable )
8
+ end
9
+ end
10
+
11
+ def self.check_ruby_version
12
+ ruby_version = parse_version( RUBY_VERSION )
13
+ if ruby_version < [ 2, 1 ]
14
+ $stderr.puts "This version of Diakonos (#{Diakonos::VERSION}) requires Ruby 2.1 or higher."
15
+ if ruby_version >= [ 2, 0 ]
16
+ $stderr.puts "Version 0.9.5 is the last version of Diakonos which can run under Ruby 2.0."
17
+ elsif ruby_version >= [ 1, 9 ]
18
+ $stderr.puts "Version 0.9.2 is the last version of Diakonos which can run under Ruby 1.9."
19
+ elsif ruby_version >= [ 1, 8 ]
20
+ $stderr.puts "Version 0.8.9 is the last version of Diakonos which can run under Ruby 1.8."
21
+ end
22
+ exit 1
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,43 @@
1
+ module Diakonos
2
+ class Window < ::Curses::Window
3
+
4
+ if $diakonos.testing
5
+
6
+ def initialize( *args )
7
+ # Setup some variables to keep track of a fake cursor
8
+ @row, @col = 0, 0
9
+ super
10
+ Curses::close_screen
11
+ end
12
+
13
+ def refresh
14
+ # Don't refresh when testing
15
+ end
16
+
17
+ def setpos( row, col )
18
+ @row, @col = row, col
19
+ end
20
+
21
+ def addstr( str )
22
+ @col += str.length
23
+ end
24
+
25
+ def curx
26
+ @col
27
+ end
28
+
29
+ def cury
30
+ @row
31
+ end
32
+
33
+ def attrset( *args )
34
+ # noop
35
+ end
36
+
37
+ def getch
38
+ $keystrokes.shift
39
+ end
40
+ end
41
+
42
+ end
43
+ end