Skip to content

Commit

Permalink
New option --numeric to print indentation for emacs
Browse files Browse the repository at this point in the history
  • Loading branch information
gpetiot committed May 12, 2020
1 parent 1fc3f80 commit abf0d2e
Show file tree
Hide file tree
Showing 16 changed files with 339 additions and 10 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
New option `--lines=X-Y` is available, that allows to format only around these lines (from X to Y included).
(#1188) (Etienne Millon)

+ Return the indentation of a set of lines (new option `--numeric`) (#1207) (Guillaume Petiot)

### 0.14.2 (2020-05-11)

#### Changes
Expand Down
71 changes: 66 additions & 5 deletions bin/ocamlformat.ml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ let print_error conf opts ~input_name e =
Translation_unit.print_error ~debug:opts.Conf.debug ~quiet:conf.Conf.quiet
~input_name e

let nth lines i =
match List.nth lines i with
| Some line -> Ok line
| None -> Error (`Msg (Format.sprintf "Cannot read line #%i." i))

open Result.Monad_infix

let run_action action opts =
match action with
| Conf.Inplace inputs ->
Expand All @@ -65,11 +72,65 @@ let run_action action opts =
Result.combine_errors_unit (List.map inputs ~f)
| In_out ({kind; file; name= input_name; conf}, output_file) -> (
let source = source_from_file file in
match format ?output_file ~kind ~input_name ~source conf opts with
| Ok s ->
to_output_file output_file s ;
Ok ()
| Error e -> Error [(fun () -> print_error conf opts ~input_name e)] )
match (opts.line_range, output_file) with
| Line_range.Only_between {low; high}, None when opts.numeric -> (
let opts = {opts with format_invalid_files= true} in
match format ~kind ~input_name ~source conf opts with
| Ok fmted -> (
match String.split_lines source with
| [] ->
Error
[ (fun () ->
Format.fprintf Format.err_formatter
"Source can not be split into lines.\n%!") ]
| lines ->
let fmted_src = Source.create fmted in
let on_line l =
nth lines (l - 1)
>>= fun line ->
let prev =
match l - 1 with
| 0 -> None
| n -> Some (String.strip (List.nth_exn lines (n - 1)))
in
let next =
if Int.equal l (List.length lines) then None
else Some (String.strip (List.nth_exn lines l))
in
let pattern = String.strip line in
Source.substr_smaller_index ?prev ?next fmted_src ~pattern
>>= fun idx ->
Source.indentation_of_index fmted_src idx
>>| fun indent ->
Stdio.print_endline (Int.to_string indent)
in
if low < high then
let rec loop i =
if i > high then Ok ()
else
match on_line i with
| Ok () -> loop (i + 1)
| Error (`Msg e) ->
Error
[ (fun () ->
Format.fprintf Format.err_formatter "%s\n%!"
e) ]
in
loop low
else
Result.map_error (on_line low) ~f:(fun (`Msg e) ->
[ (fun () ->
Format.fprintf Format.err_formatter "%s\n%!" e) ])
)
| Error e -> Error [(fun () -> print_error conf opts ~input_name e)]
)
| _ -> (
match format ?output_file ~kind ~input_name ~source conf opts with
| Ok s ->
to_output_file output_file s ;
Ok ()
| Error e -> Error [(fun () -> print_error conf opts ~input_name e)]
) )
| Check inputs ->
let f {Conf.kind; name= input_name; file; conf} =
let source = source_from_file file in
Expand Down
54 changes: 54 additions & 0 deletions emacs/ocamlformat.el
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,60 @@ function."
(delete-file bufferfile)
(delete-file outputfile)))

(defun ocamlformat-file-to-string (file)
(replace-regexp-in-string
"\n$" ""
(with-temp-buffer (insert-file-contents file) (buffer-string))))

(defun ocamlformat-args (start-line end-line)
(append
(list "--numeric"
"--lines" (format "%d-%d" start-line end-line))))

(defun ocamlformat-region (start end)
(interactive "r")
(let*
((start-line (line-number-at-pos start))
(end-line (line-number-at-pos end))
(errfile (make-temp-name (concat temporary-file-directory "ocamlformat-error")))
(indents-str
(with-output-to-string
(if (/= 0
(apply 'call-process-region
(point-min) (point-max) ocamlformat-command nil
(list standard-output errfile) nil
(ocamlformat-args start-line end-line)))
(error "Can't indent: %s returned failure" ocamlformat-command))))
(indents (mapcar 'string-to-number (split-string indents-str))))
(when (file-exists-p errfile)
(message (ocamlformat-file-to-string errfile))
(delete-file errfile))
(save-excursion
(goto-char start)
(mapcar
#'(lambda (indent) (indent-line-to indent) (forward-line))
indents))))

(defun ocamlformat-line ()
(interactive nil)
(ocamlformat-region (point) (point)))

;;;###autoload
(defun ocamlformat-setup-indent ()
(interactive nil)
(set (make-local-variable 'indent-line-function) #'ocamlformat-line))

;; (set (make-local-variable 'indent-region-function) #'ocamlformat-region)

;;;###autoload
(defun ocamlformat-caml-mode-setup ()
(ocamlformat-setup-indent)
(local-unset-key "\t")) ;; caml-mode rebinds TAB !

(add-hook 'tuareg-mode-hook 'ocamlformat-setup-indent t)

(add-hook 'caml-mode-hook 'ocamlformat-caml-mode-setup t)

(provide 'ocamlformat)

;;; ocamlformat.el ends here
14 changes: 12 additions & 2 deletions lib/Conf.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1372,6 +1372,14 @@ let name =
mk ~default
Arg.(value & opt (some string) default & info ["name"] ~doc ~docs ~docv)

let numeric =
let doc =
"Instead of re-indenting the file, output one integer per line \
representing the indentation value. Can only be used in combination \
with $(i,--lines), printing as many values as lines in the range."
in
mk ~default:false Arg.(value & flag & info ["numeric"] ~doc ~docs)

let ocp_indent_options =
let unsupported ocp_indent = (ocp_indent, ([], "")) in
let alias ocp_indent ocamlformat =
Expand Down Expand Up @@ -2288,7 +2296,8 @@ type opts =
{ debug: bool
; margin_check: bool
; format_invalid_files: bool
; line_range: Line_range.t }
; line_range: Line_range.t
; numeric: bool }
let validate () =
let root =
Expand Down Expand Up @@ -2319,7 +2328,8 @@ let validate () =
{ debug= !debug
; margin_check= !margin_check
; format_invalid_files
; line_range= !line_range }
; line_range= !line_range
; numeric= !numeric }
in
`Ok (action, opts)
Expand Down
3 changes: 2 additions & 1 deletion lib/Conf.mli
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ type opts =
; margin_check: bool
(** Check whether the formatted output exceeds the margin. *)
; format_invalid_files: bool
; line_range: Line_range.t }
; line_range: Line_range.t
; numeric: bool }

val action : unit -> (action * opts) Cmdliner.Term.result
(** Formatting action: input type and source, and output destination. *)
Expand Down
2 changes: 1 addition & 1 deletion lib/Line_range.mli
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* *
**********************************************************************)

type t
type t = All | Only_between of {low: int; high: int}

val all : t

Expand Down
68 changes: 68 additions & 0 deletions lib/Source.ml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,74 @@ type t = string

let create s = s

let rec substr_smaller_index ?prev ?next t ~pattern =
let pattern =
if String.length pattern > String.length t then
Caml.String.sub pattern 0 (String.length t)
else pattern
in
let prev_index ~prev x =
match substr_smaller_index t ~pattern:prev with
| Some p -> p < x
| None -> false
in
let next_index ~next x =
match substr_smaller_index t ~pattern:next with
| Some n -> n > x
| None -> false
in
match pattern with
| "" -> None
| _ -> (
match String.substr_index_all ~may_overlap:false t ~pattern with
| [] ->
(* pattern has not been found, we try to find a substring of it,
heuristic: cutting right (might consider another) *)
let pattern =
Caml.String.sub pattern 0 (String.length pattern - 1)
in
substr_smaller_index t ~pattern
| [idx] -> Some idx
| ids -> (
(* multiple candidates found, we have to pick the right one *)
match (prev, next) with
| None, None -> None
| Some prev, None -> List.find ids ~f:(prev_index ~prev)
| None, Some next -> List.find ids ~f:(next_index ~next)
| Some prev, Some next ->
List.find ids ~f:(fun x ->
prev_index ~prev x && next_index ~next x) ) )

let substr_smaller_index ?prev ?next t ~pattern =
match substr_smaller_index ?prev ?next t ~pattern with
| Some i -> Ok i
| None ->
Error
(`Msg (Format.sprintf "Could not find index of pattern %S." pattern))

let indentation_of_index t idx =
if idx < 0 then None
else
match String.split_lines t with
| [] -> None
| lines ->
let rec aux wc_acc = function
| [] -> None
| h :: t ->
if idx < wc_acc + String.length h then
Some (String.length h - String.(length (lstrip h)))
else aux (wc_acc + String.length h) t
in
aux 0 lines

let indentation_of_index t idx =
match indentation_of_index t idx with
| Some i -> Ok i
| None ->
Error
(`Msg
(Format.sprintf "Could not compute indentation of index %i." idx))

let position_before t (pos : Lexing.position) =
let is_closing c =
Char.equal c ')' || Char.equal c ']' || Char.equal c '}'
Expand Down
15 changes: 15 additions & 0 deletions lib/Source.mli
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@ type t

val create : string -> t

val substr_smaller_index :
?prev:string
-> ?next:string
-> t
-> pattern:string
-> (int, [`Msg of string]) Result.t
(** [substr_smaller_index ?prev ?next t ~pattern] returns the smaller index
in [t] where the bigger substring of [pattern] starts. [prev] and [next]
are the previous and next lines of [pattern] and are used to disambiguate
the result when multiple occurrences are found in [t]. *)

val indentation_of_index : t -> int -> (int, [`Msg of string]) Result.t
(** [indentation_of_index t idx] returns the indentation of the line
containing the character of index [idx] in [t]. *)

val empty_line_between : t -> Lexing.position -> Lexing.position -> bool
(** [empty_line_between t p1 p2] is [true] if there is an empty line between
[p1] and [p2]. The lines containing [p1] and [p2] are not considered
Expand Down
6 changes: 6 additions & 0 deletions ocamlformat-help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,12 @@ OPTIONS
Do not check that the version matches the one specified in
.ocamlformat.

--numeric
Instead of re-indenting the file, output one integer per line
representing the indentation value. Can only be used in
combination with --lines, printing as many values as lines in the
range.

-o DST, --output=DST
Output file. Mutually exclusive with --inplace. Write to stdout if
omitted.
Expand Down
26 changes: 26 additions & 0 deletions test/cli/dune
Original file line number Diff line number Diff line change
Expand Up @@ -443,3 +443,29 @@
(name runtest)
(action
(diff env_unknown_value.expected env_unknown_value.output)))

(rule
(targets line_indent_1.output)
(deps .ocamlformat)
(action
(with-outputs-to
%{targets}
(system "%{bin:ocamlformat} %{dep:sample/c.ml} --numeric --lines 1-1"))))

(alias
(name runtest)
(action
(diff line_indent_1.expected line_indent_1.output)))

(rule
(targets line_indent_3_7.output)
(deps .ocamlformat)
(action
(with-outputs-to
%{targets}
(system "%{bin:ocamlformat} %{dep:sample/c.ml} --numeric --lines 3-7"))))

(alias
(name runtest)
(action
(diff line_indent_3_7.expected line_indent_3_7.output)))
1 change: 1 addition & 0 deletions test/cli/line_indent_1.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0
5 changes: 5 additions & 0 deletions test/cli/line_indent_3_7.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
0
4
4
2
2
7 changes: 7 additions & 0 deletions test/cli/sample/c.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
let foooooooooooo = let foooooooo = foooooooooooo in foooooooooooo

let foooooooooooo = let foooooooooooooo =
let woooooooooooooooo = koooooooooooooooo in
baaaaaar
in
hooohoooo
Loading

0 comments on commit abf0d2e

Please sign in to comment.