@@ -110,6 +110,54 @@ class IndentationInfo(NamedTuple):
110110 def default (cls ) -> 'IndentationInfo' :
111111 return cls (4 , ' ' , 0 )
112112
113+ @classmethod
114+ def shift_indentation (cls ,
115+ content : Sequence [str ], target_lines : Sequence [str ], target_reference_indentation_count : int ,
116+ relindent_level : int | None
117+ ) -> list [str ]:
118+ """
119+ Returns 'content' with shifted indentation based on a relative indent level and a reference indentation count.
120+
121+ This method adjusts the indentation of each non-empty line in the input sequence.
122+ It calculates the difference between the target base indentation and the minimum
123+ indentation found in the content, then applies this shift to all lines.
124+
125+ Args:
126+ content (Sequence[str]): A sequence of strings representing the lines to be adjusted.
127+ target_reference_indentation_count (int): The target base indentation count to adjust to.
128+ relindent_level (int|None):
129+
130+ Returns:
131+ list[str]: A new list of strings with adjusted indentation.
132+
133+ Note:
134+ - Empty lines and lines with only whitespace are preserved as-is.
135+ - The method uses the IndentationInfo of the instance to determine
136+ the indentation character and count.
137+ - This method is useful for uniformly adjusting indentation across all lines.
138+
139+ Example:
140+ >>> info = IndentationInfo(4, ' ', 1, True)
141+ >>> lines = [" def example():", " print('Hello')"]
142+ >>> info.shift_indentation(content, 8)
143+ [' def example():', ' print('Hello')']
144+ """
145+ context_indent_char_count = cls .from_content (target_lines ).char_count
146+ return (cls .
147+ from_content (content ).
148+ _replace (char_count = context_indent_char_count ).
149+ _shift_indentation (
150+ content , target_lines , target_reference_indentation_count , relindent_level
151+ )
152+ )
153+
154+ def _shift_indentation (self ,
155+ content : Sequence [str ], target_lines : Sequence [str ], target_base_indentation_count : int , relindent_level : int | None
156+ ) -> list [str ]:
157+ target_base_indentation_count += self .char_count * (relindent_level or 0 )
158+ raw_line_adjuster = self ._shift_indentation_fun (target_base_indentation_count )
159+ return [raw_line_adjuster (line ) for line in content ]
160+
113161 @classmethod
114162 def from_content (cls , content : str | Sequence [str ]) -> 'IndentationInfo' :
115163 """
@@ -132,14 +180,16 @@ def from_content(cls, content: str | Sequence[str]) -> 'IndentationInfo':
132180 character count by analyzing patterns and using GCD.
133181 """
134182 # TODO Always send str?
135- lines = [x . lstrip () for x in content .splitlines () if x .strip ()] if isinstance (content , str ) else content
183+ lines = [x for x in content .splitlines () if x .strip ()] if isinstance (content , str ) else content
136184
137185 indentations = [extract_indentation (line ) for line in lines if line .strip ()]
186+ has_zero_indent = any ((i == '' for i in indentations ))
187+ indentations = [indent for indent in indentations if indent ]
138188
139189 if not indentations :
140190 return cls (4 , ' ' , 0 , True , "No indentation found. Assuming 4 spaces (PEP 8)." )
141191
142- indent_chars = Counter (indent [0 ] for indent in indentations if indent )
192+ indent_chars = Counter (indent [0 ] for indent in indentations )
143193 dominant_char = ' ' if indent_chars .get (' ' , 0 ) >= indent_chars .get ('\t ' , 0 ) else '\t '
144194
145195 indent_lengths = [len (indent ) for indent in indentations ]
@@ -148,20 +198,26 @@ def from_content(cls, content: str | Sequence[str]) -> 'IndentationInfo':
148198 char_count = 1
149199 else :
150200 # For spaces, determine the most likely char_count
151- space_counts = [sc for sc in indent_lengths if sc % 2 == 0 and sc > 0 ]
201+ space_counts = [sc for sc in indent_lengths if sc % 2 == 0 ]
152202 if not space_counts :
153203 char_count = 2 # Default to 2 if no even space counts
154204 else :
155- # Sort top 5 space counts and find the largest GCD
156- sorted_counts = sorted ([c [0 ] for c in Counter (space_counts ).most_common (5 )], reverse = True )
157- char_count = sorted_counts [0 ]
158- for i in range (1 , len (sorted_counts )):
159- new_gcd = gcd (char_count , sorted_counts [i ])
160- if new_gcd <= 1 :
161- break
162- char_count = new_gcd
163-
164- min_indent_chars = min (indent_lengths ) if indent_lengths else 0
205+ unique_space_counts = sorted (set (space_counts ))
206+ deltas = sorted ([b - a for a , b in zip (unique_space_counts , unique_space_counts [1 :])], reverse = True )
207+ most_common_deltas = Counter (deltas ).most_common (5 )
208+ ratio_most_common = most_common_deltas [0 ][1 ] / len (deltas )
209+ if ratio_most_common > .6 :
210+ char_count = most_common_deltas [0 ][0 ]
211+ else :
212+ char_count = deltas [0 ]
213+ # find the largest GCD
214+ for i in range (1 , len (most_common_deltas )):
215+ new_gcd = gcd (char_count , most_common_deltas [i ][0 ])
216+ if new_gcd <= 1 :
217+ break
218+ char_count = new_gcd
219+
220+ min_indent_chars = 0 if has_zero_indent else min (indent_lengths ) if indent_lengths else 0
165221 min_indent_level = min_indent_chars // char_count
166222
167223 consistency = all (len (indent ) % char_count == 0 for indent in indentations if indent )
@@ -217,42 +273,6 @@ def level_to_chars(self, level: int) -> str:
217273 """
218274 return level * self .char_count * self .char
219275
220- # TODO Revise
221- def shift_indentation (
222- self , lines : Sequence [str ], target_base_indentation_count : int , relindent_level : int | None
223- ) -> list [str ]:
224- """
225- Shift the indentation of a sequence of lines based on a target base indentation count.
226-
227- This method adjusts the indentation of each non-empty line in the input sequence.
228- It calculates the difference between the target base indentation and the minimum
229- indentation found in the content, then applies this shift to all lines.
230-
231- Args:
232- lines (Sequence[str]): A sequence of strings representing the lines to be adjusted.
233- target_base_indentation_count (int): The target base indentation count to adjust to.
234- relindent_level (int|None):
235-
236- Returns:
237- list[str]: A new list of strings with adjusted indentation.
238-
239- Note:
240- - Empty lines and lines with only whitespace are preserved as-is.
241- - The method uses the IndentationInfo of the instance to determine
242- the indentation character and count.
243- - This method is useful for uniformly adjusting indentation across all lines.
244-
245- Example:
246- >>> info = IndentationInfo(4, ' ', 1, True)
247- >>> lines = [" def example():", " print('Hello')"]
248- >>> info.shift_indentation(lines, 8)
249- [' def example():', ' print('Hello')']
250- """
251- target_base_indentation_count += self .char_count * (relindent_level or 0 )
252- raw_line_adjuster = self ._shift_indentation_fun (target_base_indentation_count )
253- # Return the transformed lines
254- return [raw_line_adjuster (line ) for line in lines ]
255-
256276 def _shift_indentation_fun (self , target_base_indentation_count : int ):
257277 # Calculate the indentation difference
258278 level_difference = self .level_difference (target_base_indentation_count )
0 commit comments