textbringer 27 → 28

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: be4c934800d0492229b1476d4541825533ff3e824490ad23ef331926ee8d6492
4
- data.tar.gz: 9157e0ba963f1dd77eab7a50f929c91b1ae6a7689c1ad41cda22876c32d6c0fb
3
+ metadata.gz: a93c844e10f5ad02a288323d8090d5dedea44b7f6050b054f9adc100e2bac129
4
+ data.tar.gz: 6a54096a278fba823491914ea9b184f1c640d64f5234192dabf64afad7ad3ce1
5
5
  SHA512:
6
- metadata.gz: 3f70f40322ad8fed8ef2044839e3df7fee67922b1cfe975283516cf3a10fac6566248d0a5fbcc98ef319155302105c0c37d9f30fa602f6a3b3b2069dbf5a1314
7
- data.tar.gz: 6348a9ecddb8423d4fb07b8291dcda1ce7945cf049e997770347deb309ad8c3a507bfb6815a5db2c7f277677680c4023793cf9fd221ea96bd4ed60e5863e68e6
6
+ metadata.gz: f19c746e74c12884bd99d827faaf0eee55a2dca803c1a2518f857286c00c91b0d7ad7d3516ce3e7e659cabd32db841674d9f42158ce01381ad9ea0b0901d71f0
7
+ data.tar.gz: 9aaf8575f47fd4e20bcd9cd868cad9b232bc7444fc29311c68d844058ab620d94a732b84ade0b527ee1ba2122f2a50f42981d17353de5c78f2c51c4535f24cd9
data/README.md CHANGED
@@ -135,7 +135,9 @@ set_background_color "default"
135
135
 
136
136
  After checking out the repo, run `bundle install` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
137
137
 
138
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
138
+ To run the tests, run `bundle exec rake test`. To install this gem onto your local machine, run `bundle exec rake install`.
139
+
140
+ To release a new version, run `bundle exec rake bump` on a clean `main` branch. It increments the version number in `version.rb`, commits and pushes the change, and pushes a `v<N>` tag. The tag triggers the `push_gem.yml` workflow, which publishes the gem to [rubygems.org](https://rubygems.org) via trusted publishing and creates a GitHub release. Do not run `bundle exec rake release`; it would try to push the gem from your machine as well.
139
141
 
140
142
  ## Contributing
141
143
 
@@ -2,32 +2,151 @@ require "stringio"
2
2
 
3
3
  module Textbringer
4
4
  module FillExtension
5
+ class Filler
6
+ GRAPH = /(?=[\u{0000}-\u{00FF}])[[:graph:]]/
7
+
8
+ def initialize(column, prefix)
9
+ @column = column
10
+ @prefix = prefix
11
+ @prefix_width = Buffer.display_width(prefix)
12
+ @fill_column = CONFIG[:fill_column]
13
+ @output = +""
14
+ @line_start = 0
15
+ @prev_c = nil
16
+ @bol = column == 0
17
+ end
18
+
19
+ # head is kept as it is at the start of the first line; body is the
20
+ # text to fill, with the prefix already removed from each line.
21
+ def fill(head, body)
22
+ if !head.empty?
23
+ @output << head
24
+ @line_start = head.size
25
+ @column += Buffer.display_width(head)
26
+ @bol = false
27
+ end
28
+ input = StringIO.new(body)
29
+ while c = input.getc
30
+ if c == "\n"
31
+ skip_blanks(input)
32
+ if input.eof?
33
+ insert_final_newline
34
+ elsif @column < @fill_column
35
+ if GRAPH.match?(@prev_c)
36
+ insert_space_between_words(input)
37
+ end
38
+ next
39
+ else
40
+ insert_newline(@prefix)
41
+ end
42
+ else
43
+ w = Buffer.display_width(c)
44
+ if @column + w > @fill_column || @column >= @fill_column
45
+ if /\w/.match?(@prev_c) && /\w/.match?(c)
46
+ insert_newline_before_word
47
+ else
48
+ insert_newline(@prefix)
49
+ end
50
+ end
51
+ insert_char(c, w)
52
+ end
53
+ @prev_c = c
54
+ end
55
+ @output
56
+ end
57
+
58
+ private
59
+
60
+ def skip_blanks(input)
61
+ while c = input.getc
62
+ if !/[ \t]/.match?(c)
63
+ input.ungetc(c)
64
+ break
65
+ end
66
+ end
67
+ end
68
+
69
+ def insert_space_between_words(input)
70
+ c = input.getc
71
+ input.ungetc(c)
72
+ if GRAPH.match?(c)
73
+ @output << " "
74
+ @column += 1
75
+ end
76
+ end
77
+
78
+ def insert_newline_before_word
79
+ m = @output.match(/(?:([^\w \t\n])|(\w)[ \t]+)(\w*)\z/, @line_start)
80
+ return if m.nil?
81
+ word = m[3]
82
+ @output[m.begin(0)..] = "#{m[1]}#{m[2]}\n#{@prefix}#{word}"
83
+ @line_start = @output.size - word.size
84
+ @column = @prefix_width + Buffer.display_width(word)
85
+ @bol = false
86
+ end
87
+
88
+ def insert_newline(prefix)
89
+ return if @bol
90
+ @output.sub!(/[ \t]+\z/, "")
91
+ @output << "\n" << prefix
92
+ @line_start = @output.size
93
+ @column = Buffer.display_width(prefix)
94
+ @bol = true
95
+ end
96
+
97
+ # The newline that ends the text is kept even on an otherwise empty
98
+ # line, and no prefix follows it.
99
+ def insert_final_newline
100
+ @output.sub!(/[ \t]+\z/, "")
101
+ @output << "\n"
102
+ @line_start = @output.size
103
+ @column = 0
104
+ @bol = true
105
+ end
106
+
107
+ def insert_char(c, w)
108
+ if @bol && /[ \t]/.match?(c)
109
+ return
110
+ end
111
+ @output << c
112
+ @column += w
113
+ @bol = false
114
+ end
115
+ end
116
+
5
117
  refine Buffer do
6
118
  def fill_region(s = Buffer.current.point, e = Buffer.current.mark)
7
119
  s, e = Buffer.region_boundaries(s, e)
8
120
  save_excursion do
9
- str = substring(s, e)
10
121
  goto_char(s)
11
122
  pos = point
12
123
  beginning_of_line
13
124
  column = Buffer.display_width(substring(point, pos))
14
- replace(fill_string(str, column), start: s, end: e)
125
+ prefix_re = fill_prefix_regexp(comment_line?)
126
+ prefix = fill_prefix(substring(point, e), prefix_re)
127
+ lines = substring(s, e).lines
128
+ head = column == 0 && lines[0] ? lines[0][prefix_re] : ""
129
+ body = lines.each_with_index.map { |line, i|
130
+ i == 0 ? line[head.size..] : line.sub(prefix_re, "")
131
+ }.join
132
+ replace(Filler.new(column, prefix).fill(head, body),
133
+ start: s, end: e)
15
134
  end
16
135
  end
17
136
 
18
137
  def fill_paragraph
19
138
  beginning_of_line
20
- while !beginning_of_buffer? &&
21
- !looking_at?(/^[ \t]*$/)
139
+ separator = fill_paragraph_separator(comment_line?)
140
+ while !beginning_of_buffer? && !looking_at?(separator)
22
141
  backward_line
23
142
  end
24
- while looking_at?(/^[ \t]*$/)
143
+ while !end_of_buffer? && looking_at?(separator)
25
144
  forward_line
26
145
  end
27
146
  s = point
28
147
  begin
29
148
  forward_line
30
- end while !end_of_buffer? && !looking_at?(/^[ \t]*$/)
149
+ end while !end_of_buffer? && !looking_at?(separator)
31
150
  if beginning_of_line?
32
151
  backward_char
33
152
  end
@@ -36,69 +155,43 @@ module Textbringer
36
155
 
37
156
  private
38
157
 
39
- def fill_string(str, column)
40
- input = StringIO.new(str)
41
- output = +""
42
- fill_column = CONFIG[:fill_column]
43
- prev_c = nil
44
- while c = input.getc
45
- if c == "\n"
46
- if column < fill_column && !input.eof?
47
- if /(?=[\u{0000}-\u{00FF}])[[:graph:]]/.match?(prev_c)
48
- column = insert_space_between_words(input, output, column)
49
- end
50
- next
51
- end
52
- column = insert_newline(output)
53
- else
54
- w = Buffer.display_width(c)
55
- if column + w > fill_column || column >= fill_column
56
- if /\w/.match?(prev_c) && /\w/.match?(c)
57
- column = insert_newline_before_word(output, column)
58
- else
59
- column = insert_newline(output)
60
- end
61
- end
62
- column = insert_char(output, column, c, w)
63
- end
64
- prev_c = c
65
- end
66
- output
158
+ # Whether the current line is a comment line of the buffer's mode.
159
+ # Point must be at the beginning of the line.
160
+ def comment_line?
161
+ re = mode&.comment_prefix_regexp
162
+ !re.nil? && looking_at?(re)
67
163
  end
68
164
 
69
- def insert_space_between_words(input, output, column)
70
- c = input.getc
71
- input.ungetc(c)
72
- if /(?=[\u{0000}-\u{00FF}])[[:graph:]]/.match?(c)
73
- output << " "
74
- column + 1
165
+ # A line that ends the paragraph: a blank line, or, when filling a
166
+ # comment, any line that is not a comment with something in it.
167
+ def fill_paragraph_separator(comment)
168
+ if comment
169
+ /^(?!#{mode.comment_prefix_regexp}[^ \t\n])/
75
170
  else
76
- column
171
+ /^[ \t]*$/
77
172
  end
78
173
  end
79
174
 
80
- def insert_newline_before_word(output, column)
81
- if output.sub!(/(?:([^\w \t\n])|(\w)[ \t]+)(\w*)\z/, "\\1\\2\n")
82
- output << $3
83
- Buffer.display_width($3)
175
+ # What is stripped from the start of each line before filling and
176
+ # put back by the prefix: indentation, and the comment leader when
177
+ # filling a comment.
178
+ def fill_prefix_regexp(comment)
179
+ if comment
180
+ /\A(?:#{mode.comment_prefix_regexp}|[ \t]*)/
84
181
  else
85
- column
182
+ /\A[ \t]*/
86
183
  end
87
184
  end
88
185
 
89
- def insert_newline(output)
90
- output.sub!(/[ \t]\z/, "")
91
- output << "\n"
92
- 0
93
- end
94
-
95
- def insert_char(output, column, c, w)
96
- if column == 0 && /[ \t]/.match?(c)
97
- column
98
- else
99
- output << c
100
- column + w
101
- end
186
+ # The prefix of the second line, or of the first line when there is
187
+ # no second line, is the prefix of every line after the first, as
188
+ # with adaptive-fill-mode in Emacs. The first line keeps its own
189
+ # prefix.
190
+ def fill_prefix(str, prefix_re)
191
+ first, second = str.lines
192
+ line = second && !second.sub(prefix_re, "").match?(/\A\n?\z/) ?
193
+ second : first
194
+ line ? line[prefix_re] : ""
102
195
  end
103
196
  end
104
197
  end