ocaml-indent / main.ml

open Pos
module State = Machine.State
module Stack = Machine.Stack

module Sexp = Sexplib.Sexp (* No open Sexplib, since Parser corrides with Sexplib.Parser *)

module Printer : sig
  val print_debug  : Lines.t -> int-> string -> unit
  val print_string : Lines.t -> int -> string -> unit
end = struct

  let print_debug t lnum s = match Lines.is_between lnum t with
    | `Before | `Over -> ()
    | `Inside -> print_string s

  (** Print a string [s] for the line number [lnum].
      If the printing text exceeds the interested region,
      It stops printing.

      Currently it cannot print a string correctly if it has
      different number of lines from the original.
  *)
  let print_string t lnum s =
    let add_line t lnum s = match Lines.is_between lnum t with
      | `Before -> ()
      | `Over -> raise Exit
      | `Inside -> print_string s
    in
    let get_line s =
      try
        let pos = String.index s '\n' in
        String.sub s 0 (pos + 1),
        Some (String.sub s (pos + 1) (String.length s - pos - 1))
      with
      | Not_found -> s, None
    in
    let rec iter lnum s =
      let line, rest =  get_line s in
      add_line t lnum line;
      match rest with
      | Some s -> iter (lnum+1) s
      | None -> ()
    in
    try iter lnum s with Exit -> ()

end

open Tokenstr
open Planck.Lazylist

let indent_file path =
  let print_string = Printer.print_string Args.lines in
  let print_debug  = Printer.print_debug  Args.lines in

  let tkstr = if path = "" then Tokenstr.of_channel stdin else Tokenstr.of_path path in
  let stream = stream tkstr in

  let flush_remaining_space info =
    print_string (Region.lnum (fst info.space)) (snd info.space)
  in

  let rec loop last_orig_region state = function
    | lazy Null _ -> state

    | lazy Cons (({ token = Parser.EOF } as info), _, _) ->
        flush_remaining_space info;
        state

    | lazy Cons (({ token = t;
                    region = orig_region;
                    space = (_space_between_region, space_between);
                    substr } as info), 
                 _, str) ->

        match Lines.is_between (Region.lnum orig_region) Args.lines with
        | `Over ->
            (* The token is outside of our interest.
               Print the remaining things and go out *)
            flush_remaining_space info;
            state
        | (`Before | `Inside as line_status) ->
            (*
              Format.eprintf "<%s %d>@."
            *)

            let last_line = Region.lnum last_orig_region in
            let current_line = Region.lnum orig_region in
            let at_new_line = last_line <>  current_line in (* Is this token at a new line? *)

            (* update the original indent if [at_new_line] *)
            let state =
              if at_new_line then { state with State.orig_indent = Region.columns orig_region }
              else state
            in

            (* Where the cursor move *)
            let _cursor_info = match Args.cursor with
              | None -> None
              | Some lines_cols ->
                  match
                    Region.contain_lines_cols last_orig_region lines_cols,
                    Region.contain_lines_cols orig_region lines_cols
                  with
                  | (`In | `Right), _ -> None                   (* far past *)
                  | `Left, `Right     -> None                   (* far future *)
                  | `Left, `Left      -> Some `In_space_between (* cursor in the space_between *)
                  | `Left, `In        -> Some `In_the_token     (* cursor on the token *)
                  | _                 -> assert false           (* It must be more tolerant, but for now... *)
            in

            let fix_indent = match line_status with
              | `Inside -> true
              | `Before -> false
              | `Over -> assert false
            in

            let pre, post = Machine.update_state state ~at_new_line ~fix_indent str orig_region t in

            (* printing *)

            if not at_new_line then print_string current_line space_between
            else begin
              (* the line 1 has no previous new line char *)
              let spaces = try String.sub space_between 0 (String.rindex space_between '\n' + 1) with _ -> "" in
              let indent_string = String.make (State.indent pre) ' ' in

              (* CR jfuruse: can be a bug. something from space_between_region *)
              print_string last_line spaces;

              if Args.debug then begin
                print_string current_line indent_string;
                if pre == post then
                  print_debug current_line
                    (Printf.sprintf "-- %s\n" (Sexp.to_string_mach (Stack.sexp_of_t pre.State.bases)))
                else
                  print_debug current_line
                    (Printf.sprintf "-- %s // %s\n"
                      (Sexp.to_string_mach (Stack.sexp_of_t pre.State.bases))
                      (Sexp.to_string_mach (Stack.sexp_of_t post.State.bases)))
              end;

              print_string current_line indent_string;
            end;

            print_string current_line substr;

            (* Now move to the next token *)
            (* CR jfuruse: last_token thing seems strange. The state machine should be able to
               access previous tokens freely. (But with risk of memory leak) *)
            let last_token = match t with Parser.COMMENT _ -> state.State.last_token | _ ->  Some t in
            let post = 
              { post with 
                State.last_token = last_token; 
                last_indent = if at_new_line then State.indent pre else post.State.last_indent }
            in

            loop orig_region post str
  in
  let final_state = loop Region.zero State.init stream in
  if Args.showstate then State.print final_state;
  Tokenstr.close tkstr

let () = List.iter indent_file Args.paths
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.