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 15, 2020
1 parent 000da99 commit b3cc4cb
Show file tree
Hide file tree
Showing 17 changed files with 312 additions and 10 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

+ Fix disabling with attributes on OCaml < 4.08 (#1322) (Etienne Millon)

#### New features

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

### 0.14.2 (2020-05-11)

#### Changes
Expand Down
29 changes: 24 additions & 5 deletions bin/ocamlformat.ml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
(** OCamlFormat *)

open Ocamlformat_lib
open Result.Monad_infix

;;
Caml.at_exit (Format.pp_print_flush Format.err_formatter)
Expand Down Expand Up @@ -42,6 +43,10 @@ let print_error conf opts ~input_name e =
Translation_unit.print_error ~debug:opts.Conf.debug ~quiet:conf.Conf.quiet
~input_name e

let convert_error =
Result.map_error ~f:(fun (`Msg e) ->
[(fun () -> Format.fprintf Format.err_formatter "%s\n%!" e)] )

let run_action action opts =
match action with
| Conf.Inplace inputs ->
Expand All @@ -65,11 +70,25 @@ 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.numeric, output_file) with
| Some range, None -> (
let opts = {opts with format_invalid_files= true} in
match format ~kind ~input_name ~source conf opts with
| Ok fmted ->
Source.indentation ~prev:(Source.create source)
~next:(Source.create fmted) ~range
>>| List.iter ~f:(fun i ->
Stdio.print_endline (Int.to_string i) )
|> convert_error
| 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
53 changes: 53 additions & 0 deletions emacs/ocamlformat.el
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,59 @@ 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" (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
25 changes: 23 additions & 2 deletions lib/Conf.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1372,6 +1372,20 @@ 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, printing as many values as lines \
in the range between lines X and Y (included)."
in
let default = None in
let docv = "X-Y" in
mk ~default
Arg.(
value
& opt (some (pair ~sep:'-' int int)) default
& info ["numeric"] ~doc ~docs ~docv)

let ocp_indent_options =
let unsupported ocp_indent = (ocp_indent, ([], "")) in
let alias ocp_indent ocamlformat =
Expand Down Expand Up @@ -2267,7 +2281,11 @@ let make_action ~enable_outside_detected_project ~root action inputs =
Ok (Check (List.map files ~f))
| `Check, `Stdin (name, kind) -> Ok (Check [make_stdin ?name kind])
type opts = {debug: bool; margin_check: bool; format_invalid_files: bool}
type opts =
{ debug: bool
; margin_check: bool
; format_invalid_files: bool
; numeric: (int * int) option }
let validate () =
let root =
Expand Down Expand Up @@ -2295,7 +2313,10 @@ let validate () =
match !format_invalid_files with Some `Auto -> true | _ -> false
in
let opts =
{debug= !debug; margin_check= !margin_check; format_invalid_files}
{ debug= !debug
; margin_check= !margin_check
; format_invalid_files
; 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 @@ -107,7 +107,8 @@ type opts =
{ debug: bool (** Generate debugging output if true. *)
; margin_check: bool
(** Check whether the formatted output exceeds the margin. *)
; format_invalid_files: bool }
; format_invalid_files: bool
; numeric: (int * int) option }

val action : unit -> (action * opts) Cmdliner.Term.result
(** Formatting action: input type and source, and output destination. *)
Expand Down
73 changes: 73 additions & 0 deletions lib/Source.ml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,85 @@
(**************************************************************************)

open Migrate_ast
open Result.Monad_infix

(** Concrete syntax. *)
type t = string

let create s = s

let to_array t = String.split_lines t |> Array.of_list

let indentation_of_line l = String.length l - String.(length (lstrip l))

(* 0 <= line < # lines *)
let indentation_exn hunks ~line =
let ranges = Patience_diff_lib.Patience_diff.Hunks.ranges hunks in
List.fold_until ranges ~init:line ~finish:Fn.id ~f:(fun acc range ->
match range with
| Same x ->
if acc < Array.length x then
let _, new_line = x.(acc) in
Stop (indentation_of_line new_line)
else Continue (acc - Array.length x)
| Prev x ->
if acc < Array.length x then
(* the line we look for is not in the formatted output *)
assert false
else Continue (acc - Array.length x)
| Next _ -> Continue acc
| Replace (x, y) ->
if acc < Array.length x then
let prev_line = String.strip x.(acc) in
match
Array.find y ~f:(fun next_line ->
let next_line = String.strip next_line in
String.is_prefix prev_line ~prefix:next_line )
with
| Some str -> Stop (indentation_of_line str)
| None -> (* should not happen *) assert false
else Continue (acc - Array.length x)
(* should not happen *)
| Unified _ -> assert false )

let indentation_one_line hunks ~line =
let err =
Format.sprintf "Could not compute indentation of line %i." line
in
match indentation_exn hunks ~line:(line - 1) with
| exception _ -> Error (`Msg err)
| i when i < 0 -> Error (`Msg err)
| i -> Ok i

let check_line lines i =
if 1 <= i && i <= List.length lines then Ok ()
else Error (`Msg (Format.sprintf "Invalid line number %i." i))

let indentation ~prev ~next ~range:(low, high) =
match String.split_lines prev with
| [] -> Error (`Msg "Source cannot be split into lines.")
| lines ->
check_line lines low
>>= fun () ->
check_line lines high
>>= fun () ->
if low > high then
Error (`Msg (Format.sprintf "Invalid range %i-%i." low high))
else
let hunks =
Patience_diff_lib.Patience_diff.String.get_hunks
~prev:(to_array prev) ~next:(to_array next)
~context:16 (* taken from patdiff *)
~big_enough:3 (* taken from patdiff *) ~transform:Fn.id
in
let rec aux acc i =
if i < low then Ok acc
else
indentation_one_line hunks ~line:i
>>= fun indent -> aux (indent :: acc) (i - 1)
in
aux [] high

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

val create : string -> t

val indentation :
prev:t
-> next:t
-> range:int * int
-> (int list, [`Msg of string]) Result.t
(** [indentation ~prev ~next ~range] returns the indentation of the range of
lines [range] (line numbers ranging from 1 to number of lines), where the
line numbers are relative to [prev] and the indentation is relative to
[next]. *)

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
2 changes: 1 addition & 1 deletion lib/dune
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@
(:standard -open Import))
;;INSERT_BISECT_HERE;;
(libraries format_ import ocaml-migrate-parsetree odoc.model odoc.parser
parse_wyc re uuseg uuseg.string))
patience_diff parse_wyc re uuseg uuseg.string))
5 changes: 5 additions & 0 deletions ocamlformat-help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,11 @@ OPTIONS
Do not check that the version matches the one specified in
.ocamlformat.

--numeric=X-Y
Instead of re-indenting the file, output one integer per line
representing the indentation value, printing as many values as
lines in the range between lines X and Y (included).

-o DST, --output=DST
Output file. Mutually exclusive with --inplace. Write to stdout if
omitted.
Expand Down
1 change: 1 addition & 0 deletions ocamlformat.opam
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ depends: [
"ocaml-migrate-parsetree" {>= "1.5.0"}
"ocp-indent" {with-test}
"odoc" {>= "1.4.2"}
"patience_diff" {>= "v0.12.0"}
"re"
"stdio"
"uuseg" {>= "10.0.0"}
Expand Down
26 changes: 26 additions & 0 deletions test/cli/dune
Original file line number Diff line number Diff line change
Expand Up @@ -520,3 +520,29 @@
(alias 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 1-1"))))

(rule
(alias 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 3-7"))))

(rule
(alias 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 b3cc4cb

Please sign in to comment.