module F = Fmt module Key = struct type special = [ `Enter | `Escape | `Tab | `Arrow of [`Up | `Down | `Left | `Right] | `Function of int | `Page of [`Up | `Down] | `Home | `End | `Insert | `Delete | `Backspace ] (** Type of key code. *) type code = [`Uchar of Uchar.t (** A unicode character. *) | special] type keystate = {ctrl: bool; meta: bool; shift: bool; super: bool; code: code} module KeyS = struct type t = keystate let compare = compare end module Bind = struct (* parts stolen from lambda-term/src/lTerm_{edit,key}.ml{,i} *) module S = Zed_input.Make (KeyS) type 'a t = 'a list S.t type 'a resolver = 'a list S.resolver type 'a result = 'a list S.result type 'a state = { mutable bindings: 'a t ; mutable state: 'a result ; mutable last_keyseq: keystate list ; mutable last_actions: 'a list } type mods = Ctrl | Meta | Super | Shift type key = C of char | U of code let keystate_of_mods ks m = List.fold_left (fun ks m -> match m with | Meta -> {ks with meta= true} | Ctrl -> {ks with ctrl= true} | Super -> {ks with super= true} | Shift -> {ks with shift= true} ) ks m let add events action bindings = let events = List.map (fun (m, k) -> keystate_of_mods { meta= false ; ctrl= false ; super= false ; shift= false ; code= ( match k with | C c -> `Uchar (Uchar.of_char c) | U c -> c ) } m ) events in S.add events action bindings let default_resolver b = S.resolver [S.pack (fun x -> x) b] let get_resolver result default = match result with S.Continue r -> r | _ -> default let init bindings = {bindings; state= S.Rejected; last_keyseq= []; last_actions= []} let resolve = S.resolve let empty = S.empty type action = | Custom of (unit -> unit) | CustomLwt of (unit -> unit Lwt.t) | Zed of Zed_edit.action let resolve_events (state : 'a state) events = List.flatten (List.filter_map (fun e -> match e with | `Key (`Press, (k : keystate)) -> ( ( match state.state with | Continue _ -> () | _ -> state.last_keyseq <- [] ) ; state.state <- resolve k (get_resolver state.state (default_resolver state.bindings) ) ; state.last_keyseq <- k :: state.last_keyseq ; match state.state with | Accepted a -> state.last_actions <- a ; Some a | Rejected -> state.last_actions <- [] ; None | _ -> None ) | _ -> None ) events ) let actions_of_events (state : action state) events = List.flatten (List.filter_map (fun e -> match e with | `Key (`Press, (k : keystate)) -> ( ( match state.state with | Continue _ -> () | _ -> state.last_keyseq <- [] ) ; state.state <- resolve k (get_resolver state.state (default_resolver state.bindings) ) ; state.last_keyseq <- k :: state.last_keyseq ; match state.state with | Accepted a -> state.last_actions <- a ; Some a | Rejected -> state.last_actions <- [] ; None | _ -> None ) | _ -> None ) events ) let process bindstate events = Lwt_list.iter_s (function | Custom f -> Lwt.return (f ()) | CustomLwt f -> f () | Zed _ -> Lwt.return_unit ) (actions_of_events bindstate events) end (* stolen from lambda-term/src/lTerm_{edit,key}.ml{,i} *) let string_of_code = function | `Uchar ch -> if Uchar.is_char ch then F.str "Char '%c'" (Uchar.to_char ch) else F.str "Char 0x%02x" (Uchar.to_int ch) | `Enter -> "Enter" | `Escape -> "Escape" | `Tab -> "Tab" | `Arrow `Up -> "Up" | `Arrow `Down -> "Down" | `Arrow `Left -> "Left" | `Arrow `Right -> "Right" | `Function i -> F.str "F%d" i | `Page `Up -> "Page Up" | `Page `Down -> "Page Down" | `Home -> "Home" | `End -> "End" | `Insert -> "Insert" | `Delete -> "Delete" | `Backspace -> "Backspace" let to_string key = Printf.sprintf "{ control = %B; meta = %B; shift = %B; super = %B; code = %s }" key.ctrl key.meta key.shift key.super (string_of_code key.code) let to_string_compact key = let buffer = Buffer.create 32 in if key.ctrl then Buffer.add_string buffer "Ctrl-" ; if key.meta then Buffer.add_string buffer "Meta-" ; if key.shift then Buffer.add_string buffer "Shift-" ; if key.super then Buffer.add_string buffer "Super-" ; ( match key.code with | `Uchar ch -> let code = Uchar.to_int ch in if Uchar.is_char ch then match Uchar.to_char ch with | ( 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' | '(' | ')' | '[' | ']' | '{' | '}' | '#' | '~' | '&' | '$' | '*' | '%' | '!' | '?' | ',' | ';' | ':' | '/' | '\\' | '.' | '@' | '=' | '+' | '-' ) as ch -> Buffer.add_char buffer ch | ' ' -> Buffer.add_string buffer "space" | _ -> Printf.bprintf buffer "U+%02x" code else if code <= 0xffff then Printf.bprintf buffer "U+%04x" code else Printf.bprintf buffer "U+%06x" code | `Page `Down -> Buffer.add_string buffer "pgup" | `Page `Up -> Buffer.add_string buffer "pgdn" | code -> Buffer.add_string buffer (String.lowercase_ascii (string_of_code code)) ) ; Buffer.contents buffer end