Skip to content

Commit 70dc63d

Browse files
authored
Add 'line-endings' option to specify line endings used in the formatted output (#1703)
1 parent 1c4960b commit 70dc63d

17 files changed

+256
-4
lines changed

.ocamlformat

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ break-cases = fit
33
margin = 77
44
parse-docstrings = true
55
wrap-comments = true
6+
line-endings = lf

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@
6868
- Break the line and reindent the cursor when pressing <ENTER>
6969
(#1639, #1685, @gpetiot) (#1687, @bcc32)
7070

71+
+ Add 'line-endings=lf|crlf' option to specify the line endings used in the
72+
formatted output. (#1703, @nojb)
73+
7174
#### Internal
7275

7376
+ A script `tools/build-mingw64.sh` is provided to build a native Windows

bin/ocamlformat/main.ml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,19 @@ let format ?output_file ~kind ~input_name ~source conf opts =
2424
Translation_unit.parse_and_format kind ?output_file ~input_name ~source
2525
conf opts
2626

27+
let write_all output_file ~data =
28+
Out_channel.with_file ~binary:true output_file ~f:(fun oc ->
29+
Out_channel.output_string oc data )
30+
2731
let to_output_file output_file data =
2832
match output_file with
29-
| None -> Out_channel.output_string Out_channel.stdout data
30-
| Some output_file -> Out_channel.write_all output_file ~data
33+
| None ->
34+
Out_channel.flush Out_channel.stdout ;
35+
Out_channel.set_binary_mode Out_channel.stdout true ;
36+
Out_channel.output_string Out_channel.stdout data ;
37+
Out_channel.flush Out_channel.stdout ;
38+
Out_channel.set_binary_mode Out_channel.stdout false
39+
| Some output_file -> write_all output_file ~data
3140

3241
let source_from_file = function
3342
| Conf.Stdin -> In_channel.input_all In_channel.stdin
@@ -52,7 +61,7 @@ let run_action action opts =
5261
match result with
5362
| Ok formatted ->
5463
if not (String.equal formatted source) then
55-
Out_channel.write_all input_file ~data:formatted ;
64+
write_all input_file ~data:formatted ;
5665
Ok ()
5766
| Error e -> Error (fun () -> print_error conf opts e)
5867
in

lib/Conf.ml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ type t =
5151
; let_binding_indent: int
5252
; let_binding_spacing: [`Compact | `Sparse | `Double_semicolon]
5353
; let_module: [`Compact | `Sparse]
54+
; line_endings: [`Lf | `Crlf]
5455
; margin: int
5556
; match_indent: int
5657
; match_indent_nested: [`Always | `Auto | `Never]
@@ -86,7 +87,7 @@ let warn_raw, collect_warnings =
8687
let delayed_warning_list = ref [] in
8788
let warn_ s =
8889
if !delay_warning then delayed_warning_list := s :: !delayed_warning_list
89-
else Format.eprintf "%s" s
90+
else Format.eprintf "%s%!" s
9091
in
9192
let collect_warnings f =
9293
let old_flag, old_list = (!delay_warning, !delayed_warning_list) in
@@ -830,6 +831,16 @@ module Formatting = struct
830831
let msg = concrete_syntax_preserved_msg in
831832
C.removed_option ~names ~version ~msg
832833

834+
let line_endings =
835+
let doc = "Line endings used." in
836+
let all =
837+
[ ("lf", `Lf, "$(b,lf) uses Unix line endings.")
838+
; ("crlf", `Crlf, "$(b,crlf) uses Windows line endings.") ]
839+
in
840+
C.choice ~names:["line-endings"] ~all ~doc ~allow_inline:false ~section
841+
(fun conf x -> {conf with line_endings= x})
842+
(fun conf -> conf.line_endings)
843+
833844
let margin =
834845
let docv = "COLS" in
835846
let doc = "Format code to fit within $(docv) columns." in
@@ -1437,6 +1448,7 @@ let ocamlformat_profile =
14371448
; let_binding_indent= 2
14381449
; let_binding_spacing= `Compact
14391450
; let_module= `Compact
1451+
; line_endings= `Lf
14401452
; margin= 80
14411453
; match_indent= 0
14421454
; match_indent_nested= `Never
@@ -1508,6 +1520,7 @@ let conventional_profile =
15081520
; let_binding_indent= C.default Formatting.let_binding_indent
15091521
; let_binding_spacing= C.default Formatting.let_binding_spacing
15101522
; let_module= C.default Formatting.let_module
1523+
; line_endings= C.default Formatting.line_endings
15111524
; margin= C.default Formatting.margin
15121525
; match_indent= C.default Formatting.match_indent
15131526
; match_indent_nested= C.default Formatting.match_indent_nested
@@ -1632,6 +1645,7 @@ let janestreet_profile =
16321645
; let_binding_indent= 2
16331646
; let_binding_spacing= `Double_semicolon
16341647
; let_module= `Sparse
1648+
; line_endings= `Lf
16351649
; margin= 90
16361650
; match_indent= 0
16371651
; match_indent_nested= `Never

lib/Conf.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ type t =
5353
; let_binding_indent: int
5454
; let_binding_spacing: [`Compact | `Sparse | `Double_semicolon]
5555
; let_module: [`Compact | `Sparse]
56+
; line_endings: [`Lf | `Crlf]
5657
; margin: int (** Format code to fit within [margin] columns. *)
5758
; match_indent: int
5859
; match_indent_nested: [`Always | `Auto | `Never]

lib/Translation_unit.ml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,30 @@ let parse_result ?(f = Ast_passes.Ast0.Parse.ast) fragment conf ~source
412412
| exception exn -> Error (Error.Invalid_source {exn; input_name})
413413
| parsed -> Ok parsed
414414

415+
let normalize_eol ~line_endings s =
416+
let buf = Buffer.create (String.length s) in
417+
let rec loop seen_cr i =
418+
if i = String.length s then (
419+
if seen_cr then Buffer.add_char buf '\r' ;
420+
Buffer.contents buf )
421+
else
422+
match (s.[i], line_endings) with
423+
| '\r', _ ->
424+
if seen_cr then Buffer.add_char buf '\r' ;
425+
loop true (i + 1)
426+
| '\n', `Crlf ->
427+
Buffer.add_string buf "\r\n" ;
428+
loop false (i + 1)
429+
| '\n', `Lf ->
430+
Buffer.add_char buf '\n' ;
431+
loop false (i + 1)
432+
| c, _ ->
433+
if seen_cr then Buffer.add_char buf '\r' ;
434+
Buffer.add_char buf c ;
435+
loop false (i + 1)
436+
in
437+
loop false 0
438+
415439
let parse_and_format (type a b) (fg0 : a Ast_passes.Ast0.t)
416440
(fgN : b Ast_passes.Ast_final.t) ?output_file ~input_name ~source conf
417441
opts =
@@ -421,6 +445,8 @@ let parse_and_format (type a b) (fg0 : a Ast_passes.Ast0.t)
421445
let parsed = {parsed with ast= Ast_passes.run fg0 fgN parsed.ast} in
422446
format fg0 fgN ?output_file ~input_name ~prev_source:source ~parsed conf
423447
opts
448+
>>= fun formatted ->
449+
Ok (normalize_eol ~line_endings:conf.Conf.line_endings formatted)
424450

425451
let parse_and_format = function
426452
| Syntax.Structure -> parse_and_format Structure Structure

ocamlformat-help.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,11 @@ OPTIONS (CODE FORMATTING STYLE)
272272
... = and before the in if the module declaration does not fit on
273273
a single line. The default value is compact.
274274

275+
--line-endings={lf|crlf}
276+
Line endings used. lf uses Unix line endings. crlf uses Windows
277+
line endings. The default value is lf. Cannot be set in
278+
attributes.
279+
275280
-m COLS, --margin=COLS
276281
Format code to fit within COLS columns. The default value is 80.
277282
Cannot be set in attributes.

test/passing/dune.inc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,30 @@
671671
(package ocamlformat)
672672
(action (diff tests/compact_lists_arrays.ml compact_lists_arrays.ml.output)))
673673

674+
(rule
675+
(deps tests/.ocamlformat )
676+
(package ocamlformat)
677+
(action
678+
(with-outputs-to crlf_to_crlf.ml.output
679+
(run %{bin:ocamlformat} --line-endings=crlf %{dep:tests/crlf_to_crlf.ml}))))
680+
681+
(rule
682+
(alias runtest)
683+
(package ocamlformat)
684+
(action (diff tests/crlf_to_crlf.ml.ref crlf_to_crlf.ml.output)))
685+
686+
(rule
687+
(deps tests/.ocamlformat )
688+
(package ocamlformat)
689+
(action
690+
(with-outputs-to crlf_to_lf.ml.output
691+
(run %{bin:ocamlformat} --line-endings=lf %{dep:tests/crlf_to_lf.ml}))))
692+
693+
(rule
694+
(alias runtest)
695+
(package ocamlformat)
696+
(action (diff tests/crlf_to_lf.ml.ref crlf_to_lf.ml.output)))
697+
674698
(rule
675699
(deps tests/.ocamlformat )
676700
(package ocamlformat)

test/passing/tests/crlf_to_crlf.ml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
let _ = {|
2+
foo
3+
4+
bar
5+
|}
6+
7+
(** This is verbatim:
8+
9+
{v
10+
o o
11+
/\ /\
12+
/\ /\
13+
v}
14+
15+
This is preformated code:
16+
17+
{[
18+
let verbatim s =
19+
s |> String.split_lines |> List.map ~f:String.strip
20+
|> fun s -> list s "@," Fmt.str
21+
]} *)
22+
23+
(** Lists:
24+
25+
list with short lines:
26+
27+
- x
28+
29+
list with long lines:
30+
31+
- xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx
32+
xxx xxx xxx xxx xxx xxx
33+
34+
list with sub lists:
35+
36+
{ul
37+
{- xxx
38+
39+
- a
40+
}
41+
} *)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--line-endings=crlf

0 commit comments

Comments
 (0)