Skip to content

Commit b2505ec

Browse files
committed
Restore auto line-splitting functionality lost during rebase
- Replace incomplete _auto_split_line with robust original implementation - Update write_formatted_line signature with indent_size and allow_split params - Integrate auto-split and inline comment detach logic into write_formatted_line - Add tests for line splitting, comment detachment, and edge cases This restores functionality from PR #4 that was lost when the rebased branches were merged back into master.
1 parent 1159aed commit b2505ec

File tree

2 files changed

+307
-34
lines changed

2 files changed

+307
-34
lines changed

fprettify/__init__.py

Lines changed: 101 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1634,8 +1634,10 @@ def reformat_ffile_combined(infile, outfile, impose_indent=True, indent_size=3,
16341634
if indent[0] < len(label):
16351635
indent = [ind + len(label) - indent[0] for ind in indent]
16361636

1637-
write_formatted_line(outfile, indent, lines, orig_lines, indent_special, llength,
1638-
use_same_line, is_omp_conditional, label, orig_filename, stream.line_nr)
1637+
allow_auto_split = auto_format and (impose_whitespace or impose_indent)
1638+
write_formatted_line(outfile, indent, lines, orig_lines, indent_special, indent_size, llength,
1639+
use_same_line, is_omp_conditional, label, orig_filename, stream.line_nr,
1640+
allow_split=allow_auto_split)
16391641

16401642
# rm subsequent blank lines
16411643
skip_blank = EMPTY_RE.search(
@@ -1912,33 +1914,71 @@ def _auto_split_line(line, ind_use, llength, indent_size):
19121914
respect the configured line-length limit. Returns a list of new line
19131915
fragments when successful, otherwise None.
19141916
"""
1915-
if len(line.rstrip('\n')) <= llength:
1917+
if llength < 40:
19161918
return None
1917-
1918-
stripped_line = line.lstrip(' ')
1919-
if not stripped_line:
1919+
1920+
stripped = line.lstrip(' ')
1921+
if not stripped:
19201922
return None
1921-
1922-
split_pos = _find_split_position(stripped_line, llength - ind_use)
1923-
if split_pos is None:
1923+
if stripped.startswith('&'):
19241924
return None
1925-
1926-
first_chunk = ' ' * ind_use + stripped_line[:split_pos] + ' &'
1927-
remaining = stripped_line[split_pos:]
1928-
1929-
new_lines = [first_chunk]
1930-
follow_indent = ind_use + indent_size
1931-
1932-
while len(remaining) > (llength - follow_indent):
1933-
split_pos = _find_split_position(remaining, llength - follow_indent)
1934-
if split_pos is None:
1925+
line_has_newline = stripped.endswith('\n')
1926+
if line_has_newline:
1927+
stripped = stripped[:-1]
1928+
1929+
has_comment = False
1930+
for _, char in CharFilter(stripped, filter_comments=False):
1931+
if char == '!':
1932+
has_comment = True
19351933
break
1936-
new_lines.append(' ' * follow_indent + remaining[:split_pos] + ' &')
1937-
remaining = remaining[split_pos:]
1938-
1939-
if remaining:
1940-
new_lines.append(' ' * follow_indent + remaining)
1941-
1934+
if has_comment:
1935+
return None
1936+
1937+
max_first = llength - ind_use - 2 # reserve for trailing ampersand
1938+
if max_first <= 0:
1939+
return None
1940+
1941+
break_pos = _find_split_position(stripped, max_first)
1942+
if break_pos is None or break_pos >= len(stripped):
1943+
return None
1944+
1945+
remainder = stripped[break_pos:].lstrip()
1946+
if not remainder:
1947+
return None
1948+
1949+
first_chunk = stripped[:break_pos].rstrip()
1950+
new_lines = [first_chunk + ' &']
1951+
1952+
current_indent = ind_use + indent_size
1953+
current = remainder
1954+
1955+
while current:
1956+
available = llength - current_indent
1957+
if available <= 0:
1958+
return None
1959+
1960+
# final chunk (fits without ampersand)
1961+
if len(current) + 2 <= available:
1962+
new_lines.append(current)
1963+
break
1964+
1965+
split_limit = available - 2 # account for ' &' suffix
1966+
if split_limit <= 0:
1967+
return None
1968+
1969+
cont_break = _find_split_position(current, split_limit)
1970+
if cont_break is None or cont_break >= len(current):
1971+
return None
1972+
1973+
chunk = current[:cont_break].rstrip()
1974+
if not chunk:
1975+
return None
1976+
new_lines.append(chunk + ' &')
1977+
current = current[cont_break:].lstrip()
1978+
1979+
if line_has_newline:
1980+
new_lines = [chunk.rstrip('\n') + '\n' for chunk in new_lines]
1981+
19421982
return new_lines
19431983

19441984

@@ -2006,10 +2046,14 @@ def _detach_inline_comment(idx, indent, lines, orig_lines):
20062046
return True
20072047

20082048

2009-
def write_formatted_line(outfile, indent, lines, orig_lines, indent_special, llength, use_same_line, is_omp_conditional, label, filename, line_nr):
2049+
def write_formatted_line(outfile, indent, lines, orig_lines, indent_special, indent_size, llength, use_same_line, is_omp_conditional, label, filename, line_nr, allow_split):
20102050
"""Write reformatted line to file"""
20112051

2012-
for ind, line, orig_line in zip(indent, lines, orig_lines):
2052+
idx = 0
2053+
while idx < len(lines):
2054+
ind = indent[idx]
2055+
line = lines[idx]
2056+
orig_line = orig_lines[idx]
20132057

20142058
# get actual line length excluding comment:
20152059
line_length = 0
@@ -2034,15 +2078,33 @@ def write_formatted_line(outfile, indent, lines, orig_lines, indent_special, lle
20342078
else:
20352079
label_use = ''
20362080

2037-
if ind_use + line_length <= (llength+1): # llength (default 132) plus 1 newline char
2081+
padding = ind_use - 3 * is_omp_conditional - len(label_use) + \
2082+
len(line) - len(line.lstrip(' '))
2083+
padding = max(0, padding)
2084+
2085+
stripped_line = line.lstrip(' ')
2086+
rendered_length = len('!$ ' * is_omp_conditional + label_use + ' ' * padding +
2087+
stripped_line.rstrip('\n'))
2088+
2089+
needs_split = allow_split and rendered_length > llength
2090+
2091+
if needs_split:
2092+
split_lines = _auto_split_line(line, ind_use, llength, indent_size)
2093+
if split_lines:
2094+
_insert_split_chunks(idx, split_lines, indent, indent_size, lines, orig_lines)
2095+
continue
2096+
if _detach_inline_comment(idx, indent, lines, orig_lines):
2097+
continue
2098+
2099+
if rendered_length <= llength:
20382100
outfile.write('!$ ' * is_omp_conditional + label_use +
2039-
' ' * (ind_use - 3 * is_omp_conditional - len(label_use) +
2040-
len(line) - len(line.lstrip(' '))) +
2041-
line.lstrip(' '))
2101+
' ' * padding + stripped_line)
20422102
elif line_length <= (llength+1):
2043-
outfile.write('!$ ' * is_omp_conditional + label_use + ' ' *
2044-
((llength+1) - 3 * is_omp_conditional - len(label_use) -
2045-
len(line.lstrip(' '))) + line.lstrip(' '))
2103+
# Recompute padding to right-align at the line length limit
2104+
padding_overflow = (llength + 1) - 3 * is_omp_conditional - len(label_use) - len(line.lstrip(' '))
2105+
padding_overflow = max(0, padding_overflow)
2106+
outfile.write('!$ ' * is_omp_conditional + label_use +
2107+
' ' * padding_overflow + line.lstrip(' '))
20462108

20472109
log_message(LINESPLIT_MESSAGE+" (limit: "+str(llength)+")", "warning",
20482110
filename, line_nr)
@@ -2051,6 +2113,11 @@ def write_formatted_line(outfile, indent, lines, orig_lines, indent_special, lle
20512113
log_message(LINESPLIT_MESSAGE+" (limit: "+str(llength)+")", "warning",
20522114
filename, line_nr)
20532115

2116+
if label:
2117+
label = ''
2118+
2119+
idx += 1
2120+
20542121

20552122
def get_curr_delim(line, pos):
20562123
"""get delimiter token in line starting at pos, if it exists"""

fprettify/tests/unittests.py

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,212 @@ def test_first_line_non_code(self):
849849
outstr = " ! a comment\n function fun()\n ! a comment\n end"
850850
self.assert_fprettify_result([], instr, outstr)
851851

852+
def test_indent_preserves_line_length_limit(self):
853+
"""indentation should remain stable when exceeding line length"""
854+
in_lines = [
855+
'subroutine demo(tokens, stmt_start)',
856+
' type(dummy), intent(in) :: tokens(:)',
857+
' integer, intent(in) :: stmt_start',
858+
' integer :: i, nesting_level',
859+
'',
860+
' if (tokens(stmt_start)%text == "if") then',
861+
' if (tokens(i)%text == "endif") then',
862+
' nesting_level = nesting_level - 1',
863+
' else if (tokens(i)%text == "end" .and. i + 1 <= size(tokens) .and. &',
864+
' tokens(i + 1)%kind == TK_KEYWORD .and. tokens(i + 1)%text == "if") then',
865+
' nesting_level = nesting_level - 1',
866+
' end if',
867+
' end if',
868+
'',
869+
' if (tokens(i)%text == "end") then',
870+
' if (i + 1 <= size(tokens) .and. tokens(i + 1)%kind == TK_KEYWORD) then',
871+
' if (tokens(i + 1)%text == "do" .and. tokens(stmt_start)%text == "do") then',
872+
' nesting_level = nesting_level - 1',
873+
' else if (tokens(i + 1)%text == "select" .and. tokens(stmt_start)%text == "select") then',
874+
' nesting_level = nesting_level - 1',
875+
' else if (tokens(i + 1)%text == "where" .and. tokens(stmt_start)%text == "where") then',
876+
' nesting_level = nesting_level - 1',
877+
' end if',
878+
' end if',
879+
' end if',
880+
'end subroutine demo',
881+
''
882+
]
883+
884+
out_lines = [
885+
'subroutine demo(tokens, stmt_start)',
886+
' type(dummy), intent(in) :: tokens(:)',
887+
' integer, intent(in) :: stmt_start',
888+
' integer :: i, nesting_level',
889+
'',
890+
' if (tokens(stmt_start)%text == "if") then',
891+
' if (tokens(i)%text == "endif") then',
892+
' nesting_level = nesting_level - 1',
893+
' else if (tokens(i)%text == "end" .and. i + 1 <= size(tokens) .and. &',
894+
' tokens(i + 1)%kind == TK_KEYWORD .and. tokens(i + 1)%text == "if") then',
895+
' nesting_level = nesting_level - 1',
896+
' end if',
897+
' end if',
898+
'',
899+
' if (tokens(i)%text == "end") then',
900+
' if (i + 1 <= size(tokens) .and. tokens(i + 1)%kind == TK_KEYWORD) then',
901+
' if (tokens(i + 1)%text == "do" .and. tokens(stmt_start)%text == "do") then',
902+
' nesting_level = nesting_level - 1',
903+
' else if (tokens(i + 1)%text == "select" .and. tokens(stmt_start)%text == &',
904+
' "select") then',
905+
' nesting_level = nesting_level - 1',
906+
' else if (tokens(i + 1)%text == "where" .and. tokens(stmt_start)%text == &',
907+
' "where") then',
908+
' nesting_level = nesting_level - 1',
909+
' end if',
910+
' end if',
911+
' end if',
912+
'end subroutine demo',
913+
''
914+
]
915+
916+
instring = '\n'.join(in_lines)
917+
outstring_exp = '\n'.join(out_lines)
918+
919+
self.assert_fprettify_result(['-l', '90'], instring, outstring_exp)
920+
921+
def test_auto_split_long_logical_line(self):
922+
"""automatically split long logical lines that exceed the limit after indentation"""
923+
instring = (
924+
"subroutine demo()\n"
925+
" integer :: a\n"
926+
" if (this_condition_is_lengthy .or. second_lengthy_condition) cycle\n"
927+
"end subroutine demo"
928+
)
929+
930+
outstring_exp = (
931+
"subroutine demo()\n"
932+
" integer :: a\n"
933+
" if (this_condition_is_lengthy .or. &\n"
934+
" second_lengthy_condition) cycle\n"
935+
"end subroutine demo"
936+
)
937+
938+
self.assert_fprettify_result(['-i', '4', '-l', '68'], instring, outstring_exp)
939+
940+
def test_auto_split_handles_bang_in_string(self):
941+
"""ensure split logic ignores exclamation marks inside string literals"""
942+
instring = (
943+
"subroutine demo(str)\n"
944+
" character(len=*), intent(in) :: str\n"
945+
" if (str .eq. \"This string has a ! bang inside\") print *, str//\", wow!\"\n"
946+
"end subroutine demo"
947+
)
948+
949+
outstring_exp = (
950+
"subroutine demo(str)\n"
951+
" character(len=*), intent(in) :: str\n"
952+
" if (str .eq. \"This string has a ! bang inside\") print *, &\n"
953+
" str//\", wow!\"\n"
954+
"end subroutine demo"
955+
)
956+
957+
self.assert_fprettify_result(['-i', '4', '-l', '72'], instring, outstring_exp)
958+
959+
def test_auto_split_after_indent_adjustment(self):
960+
"""splitting must also run during the indentation pass to stay idempotent"""
961+
instring = (
962+
"program demo\n"
963+
" integer :: i\n"
964+
" if (.true.) then\n"
965+
" if (.true.) then\n"
966+
" if (i > 1 .and. this_is_a_pretty_freaking_long_parameter_name .eq. 42) print *, \"too long\"\n"
967+
" end if\n"
968+
" end if\n"
969+
"end program demo\n"
970+
)
971+
972+
outstring_exp = (
973+
"program demo\n"
974+
" integer :: i\n"
975+
" if (.true.) then\n"
976+
" if (.true.) then\n"
977+
" if (i > 1 .and. this_is_a_pretty_freaking_long_parameter_name .eq. 42) print &\n"
978+
" *, \"too long\"\n"
979+
" end if\n"
980+
" end if\n"
981+
"end program demo\n"
982+
)
983+
984+
self.assert_fprettify_result(['-i', '4', '-l', '100'], instring, outstring_exp)
985+
986+
def test_auto_split_when_whitespace_disabled(self):
987+
"""indent-only runs must still split long logical lines"""
988+
instring = (
989+
"program demo\n"
990+
" if (.true.) then\n"
991+
" if (.true.) then\n"
992+
" if (i > 1 .and. identifier_that_is_far_too_long .eq. 42) print *, \"oops\"\n"
993+
" end if\n"
994+
" end if\n"
995+
"end program demo\n"
996+
)
997+
998+
outstring_exp = (
999+
"program demo\n"
1000+
" if (.true.) then\n"
1001+
" if (.true.) then\n"
1002+
" if (i > 1 .and. identifier_that_is_far_too_long .eq. 42) &\n"
1003+
" print *, \"oops\"\n"
1004+
" end if\n"
1005+
" end if\n"
1006+
"end program demo\n"
1007+
)
1008+
1009+
self.assert_fprettify_result(['-i', '4', '-l', '70', '--disable-whitespace'], instring, outstring_exp)
1010+
1011+
def test_line_length_detaches_inline_comment(self):
1012+
"""inline comments should move to their own line when they exceed the limit"""
1013+
instring = (
1014+
"program demo\n"
1015+
" if (.true.) then\n"
1016+
" print *, 'prefix '//'and '//'suffix' ! trailing comment\n"
1017+
" end if\n"
1018+
"end program demo\n"
1019+
)
1020+
1021+
outstring_exp = (
1022+
"program demo\n"
1023+
" if (.true.) then\n"
1024+
" print *, 'prefix '//'and '//'suffix'\n"
1025+
" ! trailing comment\n"
1026+
" end if\n"
1027+
"end program demo\n"
1028+
)
1029+
1030+
self.assert_fprettify_result(['-i', '4', '-l', '60'], instring, outstring_exp)
1031+
1032+
def test_line_length_comment_then_split(self):
1033+
"""detaching the comment must still allow the code line to split further"""
1034+
instring = (
1035+
"program demo\n"
1036+
" if (.true.) then\n"
1037+
" if (.true.) then\n"
1038+
" if (foo_bar_identifier .and. bar_baz_identifier) print *, long_identifier, another_long_identifier ! note\n"
1039+
" end if\n"
1040+
" end if\n"
1041+
"end program demo\n"
1042+
)
1043+
1044+
outstring_exp = (
1045+
"program demo\n"
1046+
" if (.true.) then\n"
1047+
" if (.true.) then\n"
1048+
" if (foo_bar_identifier .and. bar_baz_identifier) print *, &\n"
1049+
" long_identifier, another_long_identifier\n"
1050+
" ! note\n"
1051+
" end if\n"
1052+
" end if\n"
1053+
"end program demo\n"
1054+
)
1055+
1056+
self.assert_fprettify_result(['-i', '4', '-l', '72'], instring, outstring_exp)
1057+
8521058

8531059

8541060

0 commit comments

Comments
 (0)