aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/syntax_suggest/around_block_scan.rb149
-rw-r--r--lib/syntax_suggest/capture/before_after_keyword_ends.rb85
-rw-r--r--lib/syntax_suggest/capture/falling_indent_lines.rb71
-rw-r--r--lib/syntax_suggest/capture_code_context.rb18
-rw-r--r--lib/syntax_suggest/scan_history.rb16
-rw-r--r--lib/syntax_suggest/version.rb2
-rw-r--r--spec/syntax_suggest/unit/around_block_scan_spec.rb37
-rw-r--r--spec/syntax_suggest/unit/capture/before_after_keyword_ends_spec.rb47
-rw-r--r--spec/syntax_suggest/unit/capture/falling_indent_lines_spec.rb44
-rw-r--r--spec/syntax_suggest/unit/scan_history_spec.rb114
10 files changed, 404 insertions, 179 deletions
diff --git a/lib/syntax_suggest/around_block_scan.rb b/lib/syntax_suggest/around_block_scan.rb
index 346a7a99ad..ce00431b3a 100644
--- a/lib/syntax_suggest/around_block_scan.rb
+++ b/lib/syntax_suggest/around_block_scan.rb
@@ -117,133 +117,6 @@ module SyntaxSuggest
self
end
- # Shows surrounding kw/end pairs
- #
- # The purpose of showing these extra pairs is due to cases
- # of ambiguity when only one visible line is matched.
- #
- # For example:
- #
- # 1 class Dog
- # 2 def bark
- # 4 def eat
- # 5 end
- # 6 end
- #
- # In this case either line 2 could be missing an `end` or
- # line 4 was an extra line added by mistake (it happens).
- #
- # When we detect the above problem it shows the issue
- # as only being on line 2
- #
- # 2 def bark
- #
- # Showing "neighbor" keyword pairs gives extra context:
- #
- # 2 def bark
- # 4 def eat
- # 5 end
- #
- def capture_before_after_kws
- lines = []
- up_stop_next = false
- down_stop_next = false
- @scanner.commit_if_changed
-
- lines = []
- @scanner.scan(
- up: ->(line, kw_count, end_count) {
- break if up_stop_next
- next true if line.empty?
- break if line.indent < @orig_indent
- next true if line.indent != @orig_indent
-
- # If we're going up and have one complete kw/end pair, stop
- if kw_count != 0 && kw_count == end_count
- lines << line
- break
- end
-
- lines << line if line.is_kw? || line.is_end?
- },
- down: ->(line, kw_count, end_count) {
- break if down_stop_next
- next true if line.empty?
- break if line.indent < @orig_indent
- next true if line.indent != @orig_indent
-
- # if we're going down and have one complete kw/end pair,stop
- if kw_count != 0 && kw_count == end_count
- lines << line
- break
- end
-
- lines << line if line.is_kw? || line.is_end?
- }
- )
- @scanner.stash_changes
- lines
- end
-
- # Shows the context around code provided by "falling" indentation
- #
- #
- # If this is the original code lines:
- #
- # class OH
- # def hello
- # it "foo" do
- # end
- # end
- #
- # And this is the line that is captured
- #
- # it "foo" do
- #
- # It will yield its surrounding context:
- #
- # class OH
- # def hello
- # end
- # end
- #
- # Example:
- #
- # AroundBlockScan.new(
- # block: block,
- # code_lines: @code_lines
- # ).on_falling_indent do |line|
- # @lines_to_output << line
- # end
- #
- def on_falling_indent
- last_indent_up = @orig_indent
- last_indent_down = @orig_indent
-
- @scanner.commit_if_changed
- @scanner.scan(
- up: ->(line, _, _) {
- next true if line.empty?
-
- if line.indent < last_indent_up
- yield line
- last_indent_up = line.indent
- end
- true
- },
- down: ->(line, _, _) {
- next true if line.empty?
- if line.indent < last_indent_down
- yield line
- last_indent_down = line.indent
- end
- true
- }
- )
- @scanner.stash_changes
- self
- end
-
# Scanning is intentionally conservative because
# we have no way of rolling back an agressive block (at this time)
#
@@ -275,24 +148,28 @@ module SyntaxSuggest
return self if kw_count == end_count # nothing to balance
- @scanner.commit_if_changed
- scan_while { |line| line.empty? }
+ @scanner.commit_if_changed # Rollback point if we don't find anything to optimize
+
+ # Try to eat up empty lines
+ @scanner.scan(
+ up: ->(line, _, _) { line.hidden? || line.empty? },
+ down: ->(line, _, _) { line.hidden? || line.empty? }
+ )
# More ends than keywords, check if we can balance expanding up
next_up = @scanner.next_up
next_down = @scanner.next_down
- if (end_count - kw_count) == 1 && next_up
- if next_up.is_kw? && next_up.indent >= @target_indent
+ case end_count - kw_count
+ when 1
+ if next_up&.is_kw? && next_up.indent >= @target_indent
@scanner.scan(
up: ->(line, _, _) { line == next_up },
down: ->(line, _, _) { false }
)
@scanner.commit_if_changed
end
-
- # More keywords than ends, check if we can balance by expanding down
- elsif (kw_count - end_count) == 1 && next_down
- if next_down.is_end? && next_down.indent >= @target_indent
+ when -1
+ if next_down&.is_end? && next_down.indent >= @target_indent
@scanner.scan(
up: ->(line, _, _) { false },
down: ->(line, _, _) { line == next_down }
@@ -300,7 +177,7 @@ module SyntaxSuggest
@scanner.commit_if_changed
end
end
-
+ # Rollback any uncommitted changes
@scanner.stash_changes
self
diff --git a/lib/syntax_suggest/capture/before_after_keyword_ends.rb b/lib/syntax_suggest/capture/before_after_keyword_ends.rb
new file mode 100644
index 0000000000..f53c57a4d1
--- /dev/null
+++ b/lib/syntax_suggest/capture/before_after_keyword_ends.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module SyntaxSuggest
+ module Capture
+ # Shows surrounding kw/end pairs
+ #
+ # The purpose of showing these extra pairs is due to cases
+ # of ambiguity when only one visible line is matched.
+ #
+ # For example:
+ #
+ # 1 class Dog
+ # 2 def bark
+ # 4 def eat
+ # 5 end
+ # 6 end
+ #
+ # In this case either line 2 could be missing an `end` or
+ # line 4 was an extra line added by mistake (it happens).
+ #
+ # When we detect the above problem it shows the issue
+ # as only being on line 2
+ #
+ # 2 def bark
+ #
+ # Showing "neighbor" keyword pairs gives extra context:
+ #
+ # 2 def bark
+ # 4 def eat
+ # 5 end
+ #
+ #
+ # Example:
+ #
+ # lines = BeforeAfterKeywordEnds.new(
+ # block: block,
+ # code_lines: code_lines
+ # ).call()
+ #
+ class BeforeAfterKeywordEnds
+ def initialize(code_lines:, block:)
+ @scanner = ScanHistory.new(code_lines: code_lines, block: block)
+ @original_indent = block.current_indent
+ end
+
+ def call
+ lines = []
+
+ @scanner.scan(
+ up: ->(line, kw_count, end_count) {
+ next true if line.empty?
+ break if line.indent < @original_indent
+ next true if line.indent != @original_indent
+
+ # If we're going up and have one complete kw/end pair, stop
+ if kw_count != 0 && kw_count == end_count
+ lines << line
+ break
+ end
+
+ lines << line if line.is_kw? || line.is_end?
+ true
+ },
+ down: ->(line, kw_count, end_count) {
+ next true if line.empty?
+ break if line.indent < @original_indent
+ next true if line.indent != @original_indent
+
+ # if we're going down and have one complete kw/end pair,stop
+ if kw_count != 0 && kw_count == end_count
+ lines << line
+ break
+ end
+
+ lines << line if line.is_kw? || line.is_end?
+ true
+ }
+ )
+ @scanner.stash_changes
+
+ lines
+ end
+ end
+ end
+end
diff --git a/lib/syntax_suggest/capture/falling_indent_lines.rb b/lib/syntax_suggest/capture/falling_indent_lines.rb
new file mode 100644
index 0000000000..1e046b2ba5
--- /dev/null
+++ b/lib/syntax_suggest/capture/falling_indent_lines.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module SyntaxSuggest
+ module Capture
+ # Shows the context around code provided by "falling" indentation
+ #
+ # If this is the original code lines:
+ #
+ # class OH
+ # def hello
+ # it "foo" do
+ # end
+ # end
+ #
+ # And this is the line that is captured
+ #
+ # it "foo" do
+ #
+ # It will yield its surrounding context:
+ #
+ # class OH
+ # def hello
+ # end
+ # end
+ #
+ # Example:
+ #
+ # FallingIndentLines.new(
+ # block: block,
+ # code_lines: @code_lines
+ # ).call do |line|
+ # @lines_to_output << line
+ # end
+ #
+ class FallingIndentLines
+ def initialize(code_lines:, block:)
+ @lines = nil
+ @scanner = ScanHistory.new(code_lines: code_lines, block: block)
+ @original_indent = block.current_indent
+ end
+
+ def call(&yieldable)
+ last_indent_up = @original_indent
+ last_indent_down = @original_indent
+
+ @scanner.commit_if_changed
+ @scanner.scan(
+ up: ->(line, _, _) {
+ next true if line.empty?
+
+ if line.indent < last_indent_up
+ yieldable.call(line)
+ last_indent_up = line.indent
+ end
+ true
+ },
+ down: ->(line, _, _) {
+ next true if line.empty?
+
+ if line.indent < last_indent_down
+ yieldable.call(line)
+ last_indent_down = line.indent
+ end
+ true
+ }
+ )
+ @scanner.stash_changes
+ end
+ end
+ end
+end
diff --git a/lib/syntax_suggest/capture_code_context.rb b/lib/syntax_suggest/capture_code_context.rb
index 71f5b271b5..6dc7047176 100644
--- a/lib/syntax_suggest/capture_code_context.rb
+++ b/lib/syntax_suggest/capture_code_context.rb
@@ -1,6 +1,14 @@
# frozen_string_literal: true
module SyntaxSuggest
+ module Capture
+ end
+end
+
+require_relative "capture/falling_indent_lines"
+require_relative "capture/before_after_keyword_ends"
+
+module SyntaxSuggest
# Turns a "invalid block(s)" into useful context
#
# There are three main phases in the algorithm:
@@ -81,10 +89,10 @@ module SyntaxSuggest
# end
#
def capture_falling_indent(block)
- AroundBlockScan.new(
+ Capture::FallingIndentLines.new(
block: block,
code_lines: @code_lines
- ).on_falling_indent do |line|
+ ).call do |line|
@lines_to_output << line
end
end
@@ -119,8 +127,10 @@ module SyntaxSuggest
def capture_before_after_kws(block)
return unless block.visible_lines.count == 1
- around_lines = AroundBlockScan.new(code_lines: @code_lines, block: block)
- .capture_before_after_kws
+ around_lines = Capture::BeforeAfterKeywordEnds.new(
+ code_lines: @code_lines,
+ block: block
+ ).call
around_lines -= block.lines
diff --git a/lib/syntax_suggest/scan_history.rb b/lib/syntax_suggest/scan_history.rb
index 2be4aaf3c0..d15597c440 100644
--- a/lib/syntax_suggest/scan_history.rb
+++ b/lib/syntax_suggest/scan_history.rb
@@ -3,8 +3,21 @@
module SyntaxSuggest
# Scans up/down from the given block
#
- # You can snapshot a change by committing it and rolling back.
+ # You can try out a change, stash it, or commit it to save for later
#
+ # Example:
+ #
+ # scanner = ScanHistory.new(code_lines: code_lines, block: block)
+ # scanner.scan(
+ # up: ->(_, _, _) { true },
+ # down: ->(_, _, _) { true }
+ # )
+ # scanner.changed? # => true
+ # expect(scanner.lines).to eq(code_lines)
+ #
+ # scanner.stash_changes
+ #
+ # expect(scanner.lines).to_not eq(code_lines)
class ScanHistory
attr_reader :before_index, :after_index
@@ -25,6 +38,7 @@ module SyntaxSuggest
# Discards any changes that have not been committed
def stash_changes
refresh_index
+ self
end
# Discard changes that have not been committed and revert the last commit
diff --git a/lib/syntax_suggest/version.rb b/lib/syntax_suggest/version.rb
index d816cb96cf..ac8c2f62e5 100644
--- a/lib/syntax_suggest/version.rb
+++ b/lib/syntax_suggest/version.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
module SyntaxSuggest
- VERSION = "1.0.4"
+ VERSION = "1.1.0"
end
diff --git a/spec/syntax_suggest/unit/around_block_scan_spec.rb b/spec/syntax_suggest/unit/around_block_scan_spec.rb
index c9241bc8ee..d6756448bd 100644
--- a/spec/syntax_suggest/unit/around_block_scan_spec.rb
+++ b/spec/syntax_suggest/unit/around_block_scan_spec.rb
@@ -4,43 +4,6 @@ require_relative "../spec_helper"
module SyntaxSuggest
RSpec.describe AroundBlockScan do
- it "on_falling_indent" do
- source = <<~'EOM'
- class OH
- def lol
- print 'lol
- end
-
- def hello
- it "foo" do
- end
-
- def yolo
- print 'haha'
- end
- end
- EOM
-
- code_lines = CleanDocument.new(source: source).call.lines
- block = CodeBlock.new(lines: code_lines[6])
-
- lines = []
- AroundBlockScan.new(
- block: block,
- code_lines: code_lines
- ).on_falling_indent do |line|
- lines << line
- end
- lines.sort!
-
- expect(lines.join).to eq(<<~'EOM')
- class OH
- def hello
- end
- end
- EOM
- end
-
it "continues scan from last location even if scan is false" do
source = <<~'EOM'
print 'omg'
diff --git a/spec/syntax_suggest/unit/capture/before_after_keyword_ends_spec.rb b/spec/syntax_suggest/unit/capture/before_after_keyword_ends_spec.rb
new file mode 100644
index 0000000000..02d9be4387
--- /dev/null
+++ b/spec/syntax_suggest/unit/capture/before_after_keyword_ends_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require_relative "../../spec_helper"
+
+module SyntaxSuggest
+ RSpec.describe Capture::BeforeAfterKeywordEnds do
+ it "before after keyword ends" do
+ source = <<~'EOM'
+ def nope
+ print 'not me'
+ end
+
+ def lol
+ print 'lol'
+ end
+
+ def hello # 8
+
+ def yolo
+ print 'haha'
+ end
+
+ def nada
+ print 'nope'
+ end
+ EOM
+
+ code_lines = CleanDocument.new(source: source).call.lines
+ block = CodeBlock.new(lines: code_lines[8])
+
+ expect(block.to_s).to include("def hello")
+
+ lines = Capture::BeforeAfterKeywordEnds.new(
+ block: block,
+ code_lines: code_lines
+ ).call
+ lines.sort!
+
+ expect(lines.join).to include(<<~'EOM')
+ def lol
+ end
+ def yolo
+ end
+ EOM
+ end
+ end
+end
diff --git a/spec/syntax_suggest/unit/capture/falling_indent_lines_spec.rb b/spec/syntax_suggest/unit/capture/falling_indent_lines_spec.rb
new file mode 100644
index 0000000000..61d1642d97
--- /dev/null
+++ b/spec/syntax_suggest/unit/capture/falling_indent_lines_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require_relative "../../spec_helper"
+
+module SyntaxSuggest
+ RSpec.describe Capture::FallingIndentLines do
+ it "on_falling_indent" do
+ source = <<~'EOM'
+ class OH
+ def lol
+ print 'lol
+ end
+
+ def hello
+ it "foo" do
+ end
+
+ def yolo
+ print 'haha'
+ end
+ end
+ EOM
+
+ code_lines = CleanDocument.new(source: source).call.lines
+ block = CodeBlock.new(lines: code_lines[6])
+
+ lines = []
+ Capture::FallingIndentLines.new(
+ block: block,
+ code_lines: code_lines
+ ).call do |line|
+ lines << line
+ end
+ lines.sort!
+
+ expect(lines.join).to eq(<<~'EOM')
+ class OH
+ def hello
+ end
+ end
+ EOM
+ end
+ end
+end
diff --git a/spec/syntax_suggest/unit/scan_history_spec.rb b/spec/syntax_suggest/unit/scan_history_spec.rb
new file mode 100644
index 0000000000..0e75ac66ce
--- /dev/null
+++ b/spec/syntax_suggest/unit/scan_history_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require_relative "../spec_helper"
+
+module SyntaxSuggest
+ RSpec.describe ScanHistory do
+ it "retains commits" do
+ source = <<~'EOM'
+ class OH # 0
+ def lol # 1
+ print 'lol # 2
+ end # 3
+
+ def hello # 5
+ it "foo" do # 6
+ end # 7
+
+ def yolo # 8
+ print 'haha' # 9
+ end # 10
+ end
+ EOM
+
+ code_lines = CleanDocument.new(source: source).call.lines
+ block = CodeBlock.new(lines: code_lines[6])
+
+ scanner = ScanHistory.new(code_lines: code_lines, block: block)
+ scanner.scan(up: ->(_, _, _) { true }, down: ->(_, _, _) { true })
+
+ expect(scanner.changed?).to be_truthy
+ scanner.commit_if_changed
+ expect(scanner.changed?).to be_falsey
+
+ expect(scanner.lines).to eq(code_lines)
+
+ scanner.stash_changes # Assert does nothing if changes are already committed
+ expect(scanner.lines).to eq(code_lines)
+
+ scanner.revert_last_commit
+
+ expect(scanner.lines.join).to eq(code_lines[6].to_s)
+ end
+
+ it "is stashable" do
+ source = <<~'EOM'
+ class OH # 0
+ def lol # 1
+ print 'lol # 2
+ end # 3
+
+ def hello # 5
+ it "foo" do # 6
+ end # 7
+
+ def yolo # 8
+ print 'haha' # 9
+ end # 10
+ end
+ EOM
+
+ code_lines = CleanDocument.new(source: source).call.lines
+ block = CodeBlock.new(lines: code_lines[6])
+
+ scanner = ScanHistory.new(code_lines: code_lines, block: block)
+ scanner.scan(up: ->(_, _, _) { true }, down: ->(_, _, _) { true })
+
+ expect(scanner.lines).to eq(code_lines)
+ expect(scanner.changed?).to be_truthy
+ expect(scanner.next_up).to be_falsey
+ expect(scanner.next_down).to be_falsey
+
+ scanner.stash_changes
+
+ expect(scanner.changed?).to be_falsey
+
+ expect(scanner.next_up).to eq(code_lines[5])
+ expect(scanner.lines.join).to eq(code_lines[6].to_s)
+ expect(scanner.next_down).to eq(code_lines[7])
+ end
+
+ it "doesnt change if you dont't change it" do
+ source = <<~'EOM'
+ class OH # 0
+ def lol # 1
+ print 'lol # 2
+ end # 3
+
+ def hello # 5
+ it "foo" do # 6
+ end # 7
+
+ def yolo # 8
+ print 'haha' # 9
+ end # 10
+ end
+ EOM
+
+ code_lines = CleanDocument.new(source: source).call.lines
+ block = CodeBlock.new(lines: code_lines[6])
+
+ scanner = ScanHistory.new(code_lines: code_lines, block: block)
+
+ lines = scanner.lines
+ expect(scanner.changed?).to be_falsey
+ expect(scanner.next_up).to eq(code_lines[5])
+ expect(scanner.next_down).to eq(code_lines[7])
+
+ expect(scanner.stash_changes.lines).to eq(lines)
+ expect(scanner.revert_last_commit.lines).to eq(lines)
+
+ expect(scanner.scan(up: ->(_, _, _) { false }, down: ->(_, _, _) { false }).lines).to eq(lines)
+ end
+ end
+end