package stdune

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file console.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
module type Backend = Backend_intf.S

let sprintf = Printf.sprintf

module Backend = struct
  type t = Backend_intf.t

  let dumb = (module Dumb : Backend_intf.S)
  let progress = Progress.flush
  let compose = Combinators.compose
  let main = ref dumb

  let set (module T : Backend_intf.S) =
    let module Old = (val !main) in
    Old.finish ();
    main := (module T);
    T.start ()
  ;;

  let flush t = Combinators.flush t
  let progress_no_flush = Progress.no_flush
end

(* Flag that controls whether messages should be separated by a blank line *)
let separate_messages_flag = ref false

(* A user message that solely contains a blank line *)
let blank_line_msg =
  { User_message.paragraphs = [ Pp.cut ]
  ; hints = []
  ; compound = []
  ; loc = None
  ; context = None
  ; dir = None
  ; has_embedded_location = false
  ; needs_stack_trace = false
  ; promotion = None
  }
;;

(** Prints a blank line *)
let print_blank_line () =
  let (module M : Backend_intf.S) = !Backend.main in
  M.print_user_message blank_line_msg
;;

let first_msg = ref true
let separate_messages v = separate_messages_flag := v

type directory_state =
  | Not_set
  | Set of string
  | Entering_printed of string

let directory = ref Not_set

let set_directory dir =
  match !directory with
  | Not_set -> directory := Set dir
  | Set _ | Entering_printed _ -> ()
;;

(* If the [separate_messages = false], then [print_blank_line ()] does nothing.
   When [separate_messages = true], [print_blank_line ()] does nothing the
   first time it is called, whereas subsequent calls print a new line. Note
   that calls to [reset] or [reset_flush_history] will erase the information
   of whether some message has already been printed. As a consequence, after a
   call to [reset] or [reset_flush_history], [print_blank_line] will behave as
   if it has never been called before. *)
let print_blank_line () =
  if !separate_messages_flag
  then
    (* only do something when the flag is on, i.e. the first time
       the function is called *)
    if !first_msg
    then
      (* do not print anything the first time the function is
         called, but remember it has been called at least once *)
      first_msg := false
    else
      (* if the function has already been called at least once,
         print a blank line *)
      print_blank_line ()
;;

let print_user_message msg =
  let (module M : Backend_intf.S) = !Backend.main in
  (match !directory with
   | Set dir ->
     flush stdout;
     directory := Entering_printed dir;
     M.print_user_message
       (User_message.make [ Pp.verbatim (Printf.sprintf "Entering directory '%s'" dir) ])
   | Not_set | Entering_printed _ -> ());
  print_blank_line ();
  M.print_user_message msg
;;

let print paragraphs = print_user_message (User_message.make paragraphs)
let printf fmt = Printf.ksprintf (fun msg -> print [ Pp.verbatim msg ]) fmt

let set_status_line line =
  let (module M : Backend_intf.S) = !Backend.main in
  M.set_status_line line
;;

let print_if_no_status_line line =
  let (module M : Backend_intf.S) = !Backend.main in
  M.print_if_no_status_line line
;;

let reset () =
  (* forget that [print_user_message] has ever been called *)
  first_msg := true;
  let (module M : Backend_intf.S) = !Backend.main in
  M.reset ()
;;

let reset_flush_history () =
  (* forget that [print_user_message] has ever been called *)
  first_msg := true;
  let (module M : Backend_intf.S) = !Backend.main in
  M.reset_flush_history ()
;;

let finish () =
  let (module M : Backend_intf.S) = !Backend.main in
  (match !directory with
   | Entering_printed dir ->
     directory := Set dir;
     M.print_user_message
       (User_message.make [ Pp.verbatim (Printf.sprintf "Leaving directory '%s'" dir) ])
   | Not_set | Set _ -> ());
  M.finish ()
;;

let () = at_exit finish

module Status_line = struct
  type t =
    | Live of (unit -> User_message.Style.t Pp.t)
    | Constant of User_message.Style.t Pp.t

  module Id = Id.Make ()

  let toplevel = Id.gen ()
  let stack = ref []
  let sections = ref []

  let pp_if_not_nop t =
    let pp =
      match t with
      | Live f -> f ()
      | Constant x -> x
    in
    match Pp.to_ast pp with
    | Nop -> []
    | _ -> [ pp ]
  ;;

  let refresh () =
    let pps =
      let section_pps =
        List.rev !sections |> List.concat_map ~f:(fun (_id, t) -> pp_if_not_nop t)
      in
      match !stack with
      | [] -> section_pps
      | (_id, t) :: _ -> pp_if_not_nop t @ section_pps
    in
    match pps with
    | [] -> set_status_line None
    | _ :: _ ->
      let pp = Pp.concat pps ~sep:(Pp.verbatim " | ") in
      (* Always put the status line inside a horizontal box to force the
         [Format] module to prefer a single line. In particular, it seems that
         [Format.pp_print_text] split the line before the last word, unless it
         is succeeded by a space. This seems like a bug in [Format] and putting
         the whole thing into a [hbox] works around this bug.

         See https://github.com/ocaml/dune/issues/2779 *)
      set_status_line (Some (Pp.hbox pp))
  ;;

  let set t =
    stack := [ toplevel, t ];
    (match t with
     | Live _ -> ()
     | Constant pp -> print_if_no_status_line pp);
    refresh ()
  ;;

  let clear () =
    stack := [];
    refresh ()
  ;;

  type overlay = Id.t

  let add_overlay t =
    let id = Id.gen () in
    stack := (id, t) :: !stack;
    refresh ();
    id
  ;;

  let remove_overlay id =
    stack := List.filter !stack ~f:(fun (id', _) -> not (Id.equal id id'));
    refresh ()
  ;;

  let with_overlay t ~f =
    let id = add_overlay t in
    Exn.protect ~f ~finally:(fun () -> remove_overlay id)
  ;;

  type section = Id.t

  let add_section t =
    let id = Id.gen () in
    sections := (id, t) :: !sections;
    refresh ();
    id
  ;;

  let remove_section id =
    sections := List.filter !sections ~f:(fun (id', _) -> not (Id.equal id id'));
    refresh ()
  ;;
end

let () = User_warning.set_reporter print_user_message

let () =
  Log.set_forward_verbose (fun msg args ->
    let formatted_args =
      List.map args ~f:(fun (k, v) -> Printf.sprintf "%s: %s" k (Dyn.to_string v))
      |> String.concat ~sep:" "
    in
    let full_msg =
      if String.is_empty formatted_args
      then msg
      else Printf.sprintf "%s %s" msg formatted_args
    in
    print [ Pp.verbatim full_msg ])
;;

let terminal_persistence = ref Terminal_persistence.Preserve

let init (terminal_persistence' : Terminal_persistence.t) =
  terminal_persistence := terminal_persistence';
  match !terminal_persistence with
  | Preserve -> ()
  | Clear_on_rebuild -> reset ()
  | Clear_on_rebuild_and_flush_history -> reset_flush_history ()
;;

let maybe_clear_screen ~details_hum =
  match Execution_env.inside_dune with
  | true -> (* Don't print anything here to make tests less verbose *) ()
  | false ->
    (match !terminal_persistence with
     | Clear_on_rebuild -> reset ()
     | Clear_on_rebuild_and_flush_history -> reset_flush_history ()
     | Preserve ->
       let message =
         sprintf
           "********** NEW BUILD (%s) **********"
           (String.concat ~sep:", " details_hum)
       in
       print_user_message
         (User_message.make
            [ Pp.nop; Pp.tag User_message.Style.Success (Pp.verbatim message); Pp.nop ]))
;;