tree_sitter 0.1.0-aarch64-linux
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 +7 -0
- data/CHANGELOG.md +12 -0
- data/LICENSE.txt +21 -0
- data/Makefile +116 -0
- data/README.md +466 -0
- data/lib/tree_sitter/3.2/tree_sitter.so +0 -0
- data/lib/tree_sitter/3.3/tree_sitter.so +0 -0
- data/lib/tree_sitter/3.4/tree_sitter.so +0 -0
- data/lib/tree_sitter/4.0/tree_sitter.so +0 -0
- data/lib/tree_sitter/formatting.rb +236 -0
- data/lib/tree_sitter/inserter.rb +306 -0
- data/lib/tree_sitter/query_rewriter.rb +314 -0
- data/lib/tree_sitter/refactor.rb +214 -0
- data/lib/tree_sitter/rewriter.rb +155 -0
- data/lib/tree_sitter/transformer.rb +324 -0
- data/lib/tree_sitter/version.rb +5 -0
- data/lib/tree_sitter.rb +25 -0
- metadata +96 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TreeSitter
|
|
4
|
+
# Formatting utilities for syntax-aware code manipulation
|
|
5
|
+
module Formatting
|
|
6
|
+
# Detects and works with indentation in source code
|
|
7
|
+
class IndentationDetector
|
|
8
|
+
# Characters considered whitespace for indentation
|
|
9
|
+
INDENT_CHARS = [" ", "\t"].freeze
|
|
10
|
+
|
|
11
|
+
attr_reader :source, :indent_string, :indent_size, :style
|
|
12
|
+
|
|
13
|
+
# Initialize detector with source code
|
|
14
|
+
#
|
|
15
|
+
# @param source [String] The source code to analyze
|
|
16
|
+
def initialize(source)
|
|
17
|
+
@source = source
|
|
18
|
+
@lines = source.lines
|
|
19
|
+
detect
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Detect the indentation style used in the source
|
|
23
|
+
#
|
|
24
|
+
# @return [Hash] { style: :spaces|:tabs|:unknown, size: Integer, string: String }
|
|
25
|
+
def detect
|
|
26
|
+
space_indents = []
|
|
27
|
+
tab_count = 0
|
|
28
|
+
space_count = 0
|
|
29
|
+
|
|
30
|
+
@lines.each do |line|
|
|
31
|
+
next if line.strip.empty?
|
|
32
|
+
|
|
33
|
+
leading = line[/\A[ \t]*/]
|
|
34
|
+
next if leading.empty?
|
|
35
|
+
|
|
36
|
+
if leading.include?("\t")
|
|
37
|
+
tab_count += 1
|
|
38
|
+
else
|
|
39
|
+
space_count += 1
|
|
40
|
+
# Track indent sizes for space-indented lines
|
|
41
|
+
space_indents << leading.length if leading.length.positive?
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if tab_count > space_count
|
|
46
|
+
@style = :tabs
|
|
47
|
+
@indent_size = 1
|
|
48
|
+
@indent_string = "\t"
|
|
49
|
+
elsif space_count.positive?
|
|
50
|
+
@style = :spaces
|
|
51
|
+
@indent_size = detect_space_indent_size(space_indents)
|
|
52
|
+
@indent_string = " " * @indent_size
|
|
53
|
+
else
|
|
54
|
+
# Default to 4 spaces
|
|
55
|
+
@style = :spaces
|
|
56
|
+
@indent_size = 4
|
|
57
|
+
@indent_string = " "
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
{ style: @style, size: @indent_size, string: @indent_string }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Get indentation level (count) at a specific line
|
|
64
|
+
#
|
|
65
|
+
# @param line_number [Integer] Zero-based line number
|
|
66
|
+
# @return [Integer] Number of indentation units
|
|
67
|
+
def level_at_line(line_number)
|
|
68
|
+
return 0 if line_number < 0 || line_number >= @lines.length
|
|
69
|
+
|
|
70
|
+
line = @lines[line_number]
|
|
71
|
+
leading = line[/\A[ \t]*/] || ""
|
|
72
|
+
|
|
73
|
+
return leading.count("\t") if @style == :tabs
|
|
74
|
+
|
|
75
|
+
leading.length / [@indent_size, 1].max
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Get raw indentation string at a specific line
|
|
79
|
+
#
|
|
80
|
+
# @param line_number [Integer] Zero-based line number
|
|
81
|
+
# @return [String] The indentation whitespace
|
|
82
|
+
def raw_indentation_at_line(line_number)
|
|
83
|
+
return "" if line_number < 0 || line_number >= @lines.length
|
|
84
|
+
|
|
85
|
+
line = @lines[line_number]
|
|
86
|
+
line[/\A[ \t]*/] || ""
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Get indentation string at a specific byte position
|
|
90
|
+
#
|
|
91
|
+
# @param byte_pos [Integer] Byte position in source
|
|
92
|
+
# @return [String] The indentation whitespace for that line
|
|
93
|
+
def indentation_at_byte(byte_pos)
|
|
94
|
+
line_number = byte_to_line(byte_pos)
|
|
95
|
+
raw_indentation_at_line(line_number)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Get indentation level at a specific byte position
|
|
99
|
+
#
|
|
100
|
+
# @param byte_pos [Integer] Byte position in source
|
|
101
|
+
# @return [Integer] Indentation level
|
|
102
|
+
def level_at_byte(byte_pos)
|
|
103
|
+
line_number = byte_to_line(byte_pos)
|
|
104
|
+
level_at_line(line_number)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Create indentation string for a given level
|
|
108
|
+
#
|
|
109
|
+
# @param level [Integer] Indentation level
|
|
110
|
+
# @return [String] Indentation whitespace
|
|
111
|
+
def indent_string_for_level(level)
|
|
112
|
+
return "" if level <= 0
|
|
113
|
+
|
|
114
|
+
@indent_string * level
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Adjust indentation of content to a target level
|
|
118
|
+
#
|
|
119
|
+
# @param content [String] Content to adjust
|
|
120
|
+
# @param target_level [Integer] Target indentation level
|
|
121
|
+
# @param current_level [Integer, nil] Current base level (auto-detected if nil)
|
|
122
|
+
# @return [String] Re-indented content
|
|
123
|
+
def adjust_indentation(content, target_level, current_level: nil)
|
|
124
|
+
content_lines = content.lines
|
|
125
|
+
return content if content_lines.empty?
|
|
126
|
+
|
|
127
|
+
# Auto-detect current level from first non-empty line
|
|
128
|
+
if current_level.nil?
|
|
129
|
+
first_content_line = content_lines.find { |l| !l.strip.empty? }
|
|
130
|
+
if first_content_line
|
|
131
|
+
leading = first_content_line[/\A[ \t]*/] || ""
|
|
132
|
+
current_level = if @style == :tabs
|
|
133
|
+
leading.count("\t")
|
|
134
|
+
else
|
|
135
|
+
leading.length / [@indent_size, 1].max
|
|
136
|
+
end
|
|
137
|
+
else
|
|
138
|
+
current_level = 0
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
level_diff = target_level - current_level
|
|
143
|
+
|
|
144
|
+
content_lines.map do |line|
|
|
145
|
+
if line.strip.empty?
|
|
146
|
+
line
|
|
147
|
+
else
|
|
148
|
+
leading = line[/\A[ \t]*/] || ""
|
|
149
|
+
rest = line[leading.length..]
|
|
150
|
+
|
|
151
|
+
# Calculate this line's level relative to base
|
|
152
|
+
line_level = if @style == :tabs
|
|
153
|
+
leading.count("\t")
|
|
154
|
+
else
|
|
155
|
+
leading.length / [@indent_size, 1].max
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Apply the level difference
|
|
159
|
+
new_level = [line_level + level_diff, 0].max
|
|
160
|
+
indent_string_for_level(new_level) + rest
|
|
161
|
+
end
|
|
162
|
+
end.join
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Increase indentation of all lines by one level
|
|
166
|
+
#
|
|
167
|
+
# @param content [String] Content to indent
|
|
168
|
+
# @return [String] Indented content
|
|
169
|
+
def indent(content)
|
|
170
|
+
content.lines.map do |line|
|
|
171
|
+
if line.strip.empty?
|
|
172
|
+
line
|
|
173
|
+
else
|
|
174
|
+
@indent_string + line
|
|
175
|
+
end
|
|
176
|
+
end.join
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Decrease indentation of all lines by one level
|
|
180
|
+
#
|
|
181
|
+
# @param content [String] Content to dedent
|
|
182
|
+
# @return [String] Dedented content
|
|
183
|
+
def dedent(content)
|
|
184
|
+
content.lines.map do |line|
|
|
185
|
+
if line.strip.empty?
|
|
186
|
+
line
|
|
187
|
+
elsif @style == :tabs && line.start_with?("\t")
|
|
188
|
+
line[1..]
|
|
189
|
+
elsif @style == :spaces && line.start_with?(@indent_string)
|
|
190
|
+
line[@indent_size..]
|
|
191
|
+
else
|
|
192
|
+
line
|
|
193
|
+
end
|
|
194
|
+
end.join
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
private
|
|
198
|
+
|
|
199
|
+
# Detect the most common space indent size
|
|
200
|
+
def detect_space_indent_size(indents)
|
|
201
|
+
return 4 if indents.empty?
|
|
202
|
+
|
|
203
|
+
# Find GCD of all indent sizes to determine base unit
|
|
204
|
+
differences = []
|
|
205
|
+
sorted = indents.uniq.sort
|
|
206
|
+
|
|
207
|
+
sorted.each_cons(2) do |a, b|
|
|
208
|
+
differences << (b - a)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Also consider the smallest non-zero indent
|
|
212
|
+
differences << sorted.first if sorted.first&.positive?
|
|
213
|
+
|
|
214
|
+
return 4 if differences.empty?
|
|
215
|
+
|
|
216
|
+
# Find GCD
|
|
217
|
+
gcd = differences.reduce { |a, b| a.gcd(b) }
|
|
218
|
+
gcd = 4 if gcd.nil? || gcd <= 0 || gcd > 8
|
|
219
|
+
|
|
220
|
+
gcd
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Convert byte position to line number (zero-based)
|
|
224
|
+
def byte_to_line(byte_pos)
|
|
225
|
+
current_byte = 0
|
|
226
|
+
@lines.each_with_index do |line, idx|
|
|
227
|
+
line_end = current_byte + line.bytesize
|
|
228
|
+
return idx if byte_pos < line_end
|
|
229
|
+
|
|
230
|
+
current_byte = line_end
|
|
231
|
+
end
|
|
232
|
+
[@lines.length - 1, 0].max
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "formatting"
|
|
4
|
+
|
|
5
|
+
module TreeSitter
|
|
6
|
+
# Syntax-aware insertions that respect indentation and formatting.
|
|
7
|
+
#
|
|
8
|
+
# @example Insert a new statement with proper indentation
|
|
9
|
+
# Inserter.new(source, tree)
|
|
10
|
+
# .at_end_of(block_node)
|
|
11
|
+
# .insert_statement("return result;")
|
|
12
|
+
# .rewrite
|
|
13
|
+
#
|
|
14
|
+
# @example Insert a sibling function
|
|
15
|
+
# Inserter.new(source, tree)
|
|
16
|
+
# .after(existing_fn)
|
|
17
|
+
# .insert_sibling("fn new_func() {}")
|
|
18
|
+
# .rewrite
|
|
19
|
+
#
|
|
20
|
+
class Inserter
|
|
21
|
+
# Represents a pending insertion
|
|
22
|
+
Insertion = Struct.new(:byte_pos, :content, :newline_before, :newline_after, keyword_init: true)
|
|
23
|
+
|
|
24
|
+
attr_reader :source, :tree
|
|
25
|
+
|
|
26
|
+
# Initialize a new Inserter
|
|
27
|
+
#
|
|
28
|
+
# @param source [String] The source code
|
|
29
|
+
# @param tree [TreeSitter::Tree] The parsed syntax tree
|
|
30
|
+
# @param parser [TreeSitter::Parser, nil] Optional parser for re-parsing
|
|
31
|
+
def initialize(source, tree, parser: nil)
|
|
32
|
+
@source = source.dup.freeze
|
|
33
|
+
@tree = tree
|
|
34
|
+
@parser = parser
|
|
35
|
+
@indent_detector = Formatting::IndentationDetector.new(source)
|
|
36
|
+
@insertions = []
|
|
37
|
+
@insertion_point = nil
|
|
38
|
+
@insertion_context = nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Set insertion point at the beginning of a node's content (inside the node)
|
|
42
|
+
#
|
|
43
|
+
# @param node [TreeSitter::Node] Container node (e.g., a block)
|
|
44
|
+
# @return [self] For method chaining
|
|
45
|
+
def at_start_of(node)
|
|
46
|
+
# Find the first child's start, or just after the opening
|
|
47
|
+
# For blocks like { ... }, we want to insert after the opening brace
|
|
48
|
+
@insertion_context = :inside_start
|
|
49
|
+
@insertion_node = node
|
|
50
|
+
@target_indent_level = @indent_detector.level_at_byte(node.start_byte) + 1
|
|
51
|
+
|
|
52
|
+
# Find position just after opening delimiter
|
|
53
|
+
first_child = node.named_child(0)
|
|
54
|
+
if first_child
|
|
55
|
+
@insertion_point = first_child.start_byte
|
|
56
|
+
else
|
|
57
|
+
# Empty block - find end of opening line or after opening brace
|
|
58
|
+
node_text = @source[node.start_byte...node.end_byte]
|
|
59
|
+
@insertion_point = if (brace_pos = node_text.index("{"))
|
|
60
|
+
node.start_byte + brace_pos + 1
|
|
61
|
+
else
|
|
62
|
+
node.start_byte + 1
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
self
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Set insertion point at the end of a node's content (inside the node)
|
|
70
|
+
#
|
|
71
|
+
# @param node [TreeSitter::Node] Container node
|
|
72
|
+
# @return [self] For method chaining
|
|
73
|
+
def at_end_of(node)
|
|
74
|
+
@insertion_context = :inside_end
|
|
75
|
+
@insertion_node = node
|
|
76
|
+
@target_indent_level = @indent_detector.level_at_byte(node.start_byte) + 1
|
|
77
|
+
|
|
78
|
+
# Find position just before closing delimiter
|
|
79
|
+
node_text = @source[node.start_byte...node.end_byte]
|
|
80
|
+
@insertion_point = if (brace_pos = node_text.rindex("}"))
|
|
81
|
+
node.start_byte + brace_pos
|
|
82
|
+
else
|
|
83
|
+
node.end_byte
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
self
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Set insertion point before a node (as sibling)
|
|
90
|
+
#
|
|
91
|
+
# @param node [TreeSitter::Node] Reference node
|
|
92
|
+
# @return [self] For method chaining
|
|
93
|
+
def before(node)
|
|
94
|
+
@insertion_context = :before
|
|
95
|
+
@insertion_node = node
|
|
96
|
+
@insertion_point = node.start_byte
|
|
97
|
+
@target_indent_level = @indent_detector.level_at_byte(node.start_byte)
|
|
98
|
+
self
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Set insertion point after a node (as sibling)
|
|
102
|
+
#
|
|
103
|
+
# @param node [TreeSitter::Node] Reference node
|
|
104
|
+
# @return [self] For method chaining
|
|
105
|
+
def after(node)
|
|
106
|
+
@insertion_context = :after
|
|
107
|
+
@insertion_node = node
|
|
108
|
+
@insertion_point = node.end_byte
|
|
109
|
+
@target_indent_level = @indent_detector.level_at_byte(node.start_byte)
|
|
110
|
+
self
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Insert a statement with automatic indentation
|
|
114
|
+
#
|
|
115
|
+
# @param content [String] The statement to insert
|
|
116
|
+
# @param newline_before [Boolean] Add newline before (default: context-dependent)
|
|
117
|
+
# @param newline_after [Boolean] Add newline after (default: true)
|
|
118
|
+
# @return [self] For method chaining
|
|
119
|
+
def insert_statement(content, newline_before: nil, newline_after: true)
|
|
120
|
+
raise "No insertion point set. Call at_start_of, at_end_of, before, or after first." unless @insertion_point
|
|
121
|
+
|
|
122
|
+
# Determine newline_before based on context
|
|
123
|
+
newline_before = newline_before? if newline_before.nil?
|
|
124
|
+
|
|
125
|
+
# Adjust indentation of content
|
|
126
|
+
adjusted_content = adjust_content_indentation(content)
|
|
127
|
+
|
|
128
|
+
@insertions << Insertion.new(
|
|
129
|
+
byte_pos: @insertion_point,
|
|
130
|
+
content: adjusted_content,
|
|
131
|
+
newline_before: newline_before,
|
|
132
|
+
newline_after: newline_after,
|
|
133
|
+
)
|
|
134
|
+
self
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Insert raw content without indentation adjustment
|
|
138
|
+
#
|
|
139
|
+
# @param content [String] The content to insert
|
|
140
|
+
# @return [self] For method chaining
|
|
141
|
+
def insert_raw(content)
|
|
142
|
+
raise "No insertion point set. Call at_start_of, at_end_of, before, or after first." unless @insertion_point
|
|
143
|
+
|
|
144
|
+
@insertions << Insertion.new(
|
|
145
|
+
byte_pos: @insertion_point,
|
|
146
|
+
content: content,
|
|
147
|
+
newline_before: false,
|
|
148
|
+
newline_after: false,
|
|
149
|
+
)
|
|
150
|
+
self
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Insert a sibling node with matching indentation
|
|
154
|
+
#
|
|
155
|
+
# @param content [String] The sibling content
|
|
156
|
+
# @param separator [String] Separator between siblings (default: newlines based on context)
|
|
157
|
+
# @return [self] For method chaining
|
|
158
|
+
def insert_sibling(content, separator: nil)
|
|
159
|
+
raise "No insertion point set. Call before or after first." unless @insertion_point
|
|
160
|
+
|
|
161
|
+
separator ||= "\n\n" # Default to blank line between top-level items
|
|
162
|
+
|
|
163
|
+
# Adjust indentation of content
|
|
164
|
+
adjusted_content = adjust_content_indentation(content)
|
|
165
|
+
|
|
166
|
+
# For after insertion, add separator before content
|
|
167
|
+
# For before insertion, add separator after content
|
|
168
|
+
full_content = case @insertion_context
|
|
169
|
+
when :after
|
|
170
|
+
separator + adjusted_content
|
|
171
|
+
when :before
|
|
172
|
+
adjusted_content + separator
|
|
173
|
+
else
|
|
174
|
+
adjusted_content
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
@insertions << Insertion.new(
|
|
178
|
+
byte_pos: @insertion_point,
|
|
179
|
+
content: full_content,
|
|
180
|
+
newline_before: false,
|
|
181
|
+
newline_after: false,
|
|
182
|
+
)
|
|
183
|
+
self
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Insert a block with proper indentation (for block constructs)
|
|
187
|
+
#
|
|
188
|
+
# @param header [String] The block header (e.g., "if condition")
|
|
189
|
+
# @param body [String] The block body content (will be indented)
|
|
190
|
+
# @param open_brace [String] Opening delimiter (default: " {")
|
|
191
|
+
# @param close_brace [String] Closing delimiter (default: "}")
|
|
192
|
+
# @return [self] For method chaining
|
|
193
|
+
def insert_block(header, body, open_brace: " {", close_brace: "}")
|
|
194
|
+
raise "No insertion point set. Call at_start_of, at_end_of, before, or after first." unless @insertion_point
|
|
195
|
+
|
|
196
|
+
indent = @indent_detector.indent_string_for_level(@target_indent_level)
|
|
197
|
+
body_indent = @indent_detector.indent_string_for_level(@target_indent_level + 1)
|
|
198
|
+
|
|
199
|
+
# Build the block with proper indentation
|
|
200
|
+
indented_body = body.lines.map do |line|
|
|
201
|
+
if line.strip.empty?
|
|
202
|
+
line
|
|
203
|
+
else
|
|
204
|
+
body_indent + line.lstrip
|
|
205
|
+
end
|
|
206
|
+
end.join
|
|
207
|
+
|
|
208
|
+
block_content = "#{indent}#{header}#{open_brace}\n#{indented_body}\n#{indent}#{close_brace}"
|
|
209
|
+
|
|
210
|
+
newline_before = newline_before?
|
|
211
|
+
|
|
212
|
+
@insertions << Insertion.new(
|
|
213
|
+
byte_pos: @insertion_point,
|
|
214
|
+
content: block_content,
|
|
215
|
+
newline_before: newline_before,
|
|
216
|
+
newline_after: true,
|
|
217
|
+
)
|
|
218
|
+
self
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Apply all insertions
|
|
222
|
+
#
|
|
223
|
+
# @return [String] The source with insertions
|
|
224
|
+
def rewrite
|
|
225
|
+
return @source if @insertions.empty?
|
|
226
|
+
|
|
227
|
+
# Sort by position descending to apply from end to start
|
|
228
|
+
sorted = @insertions.sort_by { |ins| -ins.byte_pos }
|
|
229
|
+
|
|
230
|
+
result = @source.dup
|
|
231
|
+
sorted.each do |insertion|
|
|
232
|
+
content = insertion.content
|
|
233
|
+
content = "\n#{content}" if insertion.newline_before
|
|
234
|
+
content = "#{content}\n" if insertion.newline_after
|
|
235
|
+
|
|
236
|
+
result.insert(insertion.byte_pos, content)
|
|
237
|
+
end
|
|
238
|
+
result
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Apply insertions and return both source and new tree
|
|
242
|
+
#
|
|
243
|
+
# @return [Array<String, Tree>] The new source and re-parsed tree
|
|
244
|
+
def rewrite_with_tree
|
|
245
|
+
new_source = rewrite
|
|
246
|
+
|
|
247
|
+
parser = @parser || create_parser_from_tree
|
|
248
|
+
raise "No parser available for re-parsing" unless parser
|
|
249
|
+
|
|
250
|
+
new_tree = parser.parse(new_source)
|
|
251
|
+
[new_source, new_tree]
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Reset insertion point to allow setting a new one
|
|
255
|
+
#
|
|
256
|
+
# @return [self] For method chaining
|
|
257
|
+
def reset_position
|
|
258
|
+
@insertion_point = nil
|
|
259
|
+
@insertion_context = nil
|
|
260
|
+
@insertion_node = nil
|
|
261
|
+
@target_indent_level = nil
|
|
262
|
+
self
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
private
|
|
266
|
+
|
|
267
|
+
def newline_before?
|
|
268
|
+
case @insertion_context
|
|
269
|
+
when :inside_start
|
|
270
|
+
# After opening brace, usually need newline
|
|
271
|
+
# But check if there's already content on the same line
|
|
272
|
+
true
|
|
273
|
+
when :inside_end
|
|
274
|
+
# Before closing brace, check if we need newline
|
|
275
|
+
# Look at what's before the insertion point
|
|
276
|
+
before_text = @source[0...@insertion_point]
|
|
277
|
+
last_newline = before_text.rindex("\n")
|
|
278
|
+
content_after_newline = last_newline ? before_text[(last_newline + 1)..] : before_text
|
|
279
|
+
# If there's only whitespace after the last newline, we might not need another
|
|
280
|
+
!content_after_newline.strip.empty?
|
|
281
|
+
when :before, :after
|
|
282
|
+
# Siblings usually don't need newline before (handled by separator)
|
|
283
|
+
false
|
|
284
|
+
else
|
|
285
|
+
true
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def adjust_content_indentation(content)
|
|
290
|
+
return content if content.strip.empty?
|
|
291
|
+
|
|
292
|
+
@indent_detector.adjust_indentation(content.strip, @target_indent_level, current_level: 0)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def create_parser_from_tree
|
|
296
|
+
return unless @tree
|
|
297
|
+
|
|
298
|
+
parser = TreeSitter::Parser.new
|
|
299
|
+
lang = @tree.language
|
|
300
|
+
parser.language = lang.name if lang
|
|
301
|
+
parser
|
|
302
|
+
rescue StandardError
|
|
303
|
+
nil
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
end
|