diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..9b9682b --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,44 @@ +name: Install, test and format + +on: + push: + branches: + - "main" + pull_request: + branches: + - "main" + +permissions: read-all + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: + - macos-latest + - ubuntu-latest + ocaml-compiler: + - "5.1" + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set-up OCaml version:${{ matrix.ocaml-compiler }} in os:${{ matrix.os }} + uses: ocaml/setup-ocaml@v2 + with: + ocaml-compiler: ${{ matrix.ocaml-compiler }} + + - name: Install Dependencies + run: | + opam install --deps-only -t -y . + opam install ocamlformat.0.26.1 + + - name: Test + run: opam exec -- dune runtest + + - name: Format + run: opam exec -- dune build @fmt diff --git a/.github/workflows/ocaml-ci-setup.yml b/.github/workflows/ocaml-ci-setup.yml new file mode 100644 index 0000000..95966f4 --- /dev/null +++ b/.github/workflows/ocaml-ci-setup.yml @@ -0,0 +1,50 @@ +name: CI Run test + +on: + push: + branches: + - 'main' + pull_request: + branches: + - 'main' + +jobs: +# See: https://github.com/ocaml/setup-ocaml + setup-ocaml: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Setup OCaml environment + run: setup-ocaml + + install-dependencies: + runs-on: ubuntu-latest + needs: setup-ocaml + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Install project dependencies + run: opam install --deps-only -t -y + + run-tests: + runs-on: ubuntu-latest + needs: install-dependencies + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Run tests + run: dune runtest + + check-formatting: + runs-on: ubuntu-latest + needs: install-dependencies + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Check formatting + env: + OCAMLFORMAT: "0.26.1" + run: dune build @fmt + + diff --git a/lib/interactive_viewer.ml b/lib/interactive_viewer.ml index 1c58750..b33f1e2 100644 --- a/lib/interactive_viewer.ml +++ b/lib/interactive_viewer.ml @@ -110,12 +110,19 @@ let view (patches : Patch.t list) = | None -> failwith "zipper_of_list: empty list" in let curr_scroll_state = Lwd.var W.default_scroll_state in + let curr_scroll_state_1 = Lwd.var W.default_scroll_state in let change_scroll_state _action state = - let off_screen = state.W.position > state.W.bound in - if off_screen then + let y_off_screen = state.W.position > state.W.bound in + if y_off_screen then Lwd.set curr_scroll_state { state with position = state.W.bound } else Lwd.set curr_scroll_state state in + let change_scroll_state_1 _action state = + let x_off_screen = state.W.position > state.W.bound in + if x_off_screen then + Lwd.set curr_scroll_state_1 { state with position = state.W.bound } + else Lwd.set curr_scroll_state_1 state + in W.vbox [ operation_info z_patches; @@ -125,6 +132,10 @@ let view (patches : Patch.t list) = ~state:(Lwd.get curr_scroll_state) ~change:change_scroll_state @@ current_hunks z_patches; + W.hscroll_area + ~state:(Lwd.get curr_scroll_state_1) + ~change:change_scroll_state_1 + @@ current_hunks z_patches; Lwd.pure @@ Ui.keyboard_area (function diff --git a/vendor/lwd/lib/nottui/nottui_widgets.ml b/vendor/lwd/lib/nottui/nottui_widgets.ml index c67e9c1..ad29c07 100644 --- a/vendor/lwd/lib/nottui/nottui_widgets.ml +++ b/vendor/lwd/lib/nottui/nottui_widgets.ml @@ -141,6 +141,16 @@ let menu_overlay wm g ?(dx=0) ?(dy=0) body around = let scroll_step = 1 +(* type scroll_state = { + y_position: int; + x_position: int; + y_bound : int; + x_bound : int; + y_visible : int; + x_visible : int; + y_total : int; + x_total : int; +} *) type scroll_state = { position: int; bound : int; @@ -148,50 +158,100 @@ type scroll_state = { total : int; } -let default_scroll_state = { position = 0; bound = 0; visible = 0; total = 0 } + +(* let default_scroll_state = { y_position = 0; y_bound = 0; y_visible = 0; y_total = 0; x_position = 0; x_bound = 0; x_visible = 0; x_total = 0; } *) +let default_scroll_state = { position = 0; bound = 0; visible = 0; total = 0; } let vscroll_area ~state ~change t = - let visible = ref (-1) in - let total = ref (-1) in - let scroll state delta = - let position = state.position + delta in - let position = clampi position ~min:0 ~max:state.bound in - if position <> state.position then - change `Action {state with position}; - `Handled + let y_visible = ref (-1) in + let y_total = ref (-1) in + let scroll state delta del = + let y_position = state.position + delta in + let position = clampi y_position ~min:0 ~max:state.bound in + if position <> state.position then + change `Action {state with position}; + `Handled in let focus_handler state = function - (*| `Arrow `Left , _ -> scroll (-scroll_step) 0*) - (*| `Arrow `Right, _ -> scroll (+scroll_step) 0*) - | `Arrow `Up , [] -> scroll state (-scroll_step) - | `Arrow `Down , [] -> scroll state (+scroll_step) - | `Page `Up, [] -> scroll state ((-scroll_step) * 8) - | `Page `Down, [] -> scroll state ((+scroll_step) * 8) + | `Arrow `Left , [] -> scroll state (0) (-scroll_step) + | `Arrow `Right, [] -> scroll state (0) (+scroll_step) + | `Arrow `Up , [] -> scroll state (-scroll_step) (0) + | `Arrow `Down , [] -> scroll state (+scroll_step) (0) + | `Page `Up, [] -> scroll state ((-scroll_step) * 8) (0) + | `Page `Down, [] -> scroll state ((+scroll_step) * 8) (0) | _ -> `Unhandled in let scroll_handler state ~x:_ ~y:_ = function - | `Scroll `Up -> scroll state (-scroll_step) - | `Scroll `Down -> scroll state (+scroll_step) + | `Scroll `Up -> scroll state (-scroll_step) (0) + | `Scroll `Down -> scroll state (+scroll_step) (0) + | _ -> `Unhandled in Lwd.map2 t state ~f:begin fun t state -> t |> Ui.shift_area 0 state.position |> Ui.resize ~h:0 ~sh:1 - |> Ui.size_sensor (fun ~w:_ ~h -> - let tchange = - if !total <> (Ui.layout_spec t).Ui.h - then (total := (Ui.layout_spec t).Ui.h; true) + |> Ui.size_sensor (fun ~w ~h -> + let tychange = + if !y_total <> (Ui.layout_spec t).Ui.h + then (y_total := (Ui.layout_spec t).Ui.h; true) + else false + in + let vychange = + if !y_visible <> h + then (y_visible := h; true) + else false + in + if tychange || vychange then + change `Content {state with visible = !y_visible; total = !y_total; + bound = maxi 0 (!y_total - !y_visible);} + ) + |> Ui.mouse_area (scroll_handler state) + |> Ui.keyboard_area (focus_handler state) + end +let hscroll_area ~state ~change t = + let x_visible = ref (-1) in + let x_total = ref (-1) in + let scroll state delta del = + let x_position = state.position + del in + let position = clampi x_position ~min:0 ~max:state.bound in + if x_position <> state.position then + change `Action {state with position}; + `Handled + in + let focus_handler state = function + | `Arrow `Left , [] -> scroll state (0) (-scroll_step) + | `Arrow `Right, [] -> scroll state (0) (+scroll_step) + | `Arrow `Up , [] -> scroll state (-scroll_step) (0) + | `Arrow `Down , [] -> scroll state (+scroll_step) (0) + | `Page `Up, [] -> scroll state ((-scroll_step) * 8) (0) + | `Page `Down, [] -> scroll state ((+scroll_step) * 8) (0) + | _ -> `Unhandled + in + let scroll_handler state ~x:_ ~y:_ = function + | `Scroll `Up -> scroll state (-scroll_step) (0) + | `Scroll `Down -> scroll state (+scroll_step) (0) + + | _ -> `Unhandled + in + Lwd.map2 t state ~f:begin fun t state -> + t + |> Ui.shift_area state.position 0 + |> Ui.resize ~w:0 ~sw:1 + |> Ui.size_sensor (fun ~w ~h -> + let txchange = + if !x_total <> (Ui.layout_spec t).Ui.w + then (x_total := (Ui.layout_spec t).Ui.w; true) else false in - let vchange = - if !visible <> h - then (visible := h; true) + let vxchange = + if !x_visible <> w + then (x_visible := w; true) else false in - if tchange || vchange then - change `Content {state with visible = !visible; total = !total; - bound = maxi 0 (!total - !visible); } + if txchange || vxchange then + change `Content {state with visible = !x_visible; total = !x_total; + bound = maxi 0 (!x_total - !x_visible); } ) |> Ui.mouse_area (scroll_handler state) |> Ui.keyboard_area (focus_handler state) diff --git a/vendor/lwd/lib/nottui/nottui_widgets.mli b/vendor/lwd/lib/nottui/nottui_widgets.mli index 071a764..8165a89 100644 --- a/vendor/lwd/lib/nottui/nottui_widgets.mli +++ b/vendor/lwd/lib/nottui/nottui_widgets.mli @@ -31,14 +31,19 @@ val sub_entry : string -> (unit -> unit) -> ui (* FIXME Explain how scrolling works *) val scroll_step : int -type scroll_state = { position : int; bound : int; visible : int; total : int } +type scroll_state = { position : int; bound : int; visible : int; total : int; } val default_scroll_state : scroll_state val vscroll_area : state:scroll_state Lwd.t -> change:([> `Action | `Content ] -> scroll_state -> unit) -> ui Lwd.t -> ui Lwd.t -val scroll_area : +val hscroll_area : + state:scroll_state Lwd.t -> + change:([> `Action | `Content ] -> scroll_state -> unit) -> + ui Lwd.t -> ui Lwd.t + + val scroll_area : ?offset:int * int -> ui Lwd.t -> ui Lwd.t val scrollbox: ui Lwd.t -> ui Lwd.t