Source file major_mode.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
open! Core
open! Import
open! Async_kernel
include Major_mode_intf
module Hook = Hook0

module Q = struct
  include Q

  let define_derived_mode = "define-derived-mode" |> Symbol.intern
end

module Current_buffer = Current_buffer0

type t =
  { wrapped_at : Source_code_position.t
  ; symbol : Symbol.t
  ; keymap_var : Keymap.t Var.t
  ; name : (Name.t[@sexp.opaque])
  ; hook : Hook.normal Hook.t Or_error.t
  ; syntax_table_var : Syntax_table.t Var.t
  }
[@@deriving fields, sexp_of]

let equal t1 t2 = Symbol.equal t1.symbol t2.symbol
let compare_name t1 t2 = Symbol.compare_name t1.symbol t2.symbol
let t_by_symbol : t String.Table.t = Hashtbl.create (module String)

include Intf (struct
    type nonrec t = t
  end)

module Compare_by_name = struct
  type nonrec t = t [@@deriving sexp_of]

  let to_string = symbol >> Symbol.name
  let hash = to_string >> String.hash
  let hash_fold_t state t = String.hash_fold_t state (to_string t)
  let equal t1 t2 = String.equal (to_string t1) (to_string t2)
  let compare a b = Comparable.lift String.compare ~f:to_string a b
end

let major_mode_var = Buffer_local.Wrap.("major-mode" <: Symbol.t)

let change_to t ~in_:buffer =
  Value.Private.run_outside_async [%here] ~allowed_in_background:true (fun () ->
    Current_buffer.set_temporarily Sync buffer ~f:(fun () ->
      Funcall.Wrap.(symbol t |> Symbol.name <: nullary @-> return nil) ()))
;;

let add wrapped_at name symbol =
  let hook =
    let fundamental_mode = Symbol.intern "fundamental-mode" in
    match [%compare.equal: Symbol.Compare_name.t] symbol fundamental_mode with
    | false -> Ok Hook.Wrap.(concat [ symbol |> Symbol.name; "-hook" ] <: Normal_hook)
    | true ->
      error_s
        [%message
          {|fundamental-mode has no mode hook. [(Info-goto-node "(elisp) Major Modes")]|}]
  in
  let keymap_var = Var.Wrap.(concat [ symbol |> Symbol.name; "-map" ] <: Keymap.t) in
  let syntax_table_var =
    Var.Wrap.(concat [ symbol |> Symbol.name; "-syntax-table" ] <: Syntax_table.t)
  in
  let t = { wrapped_at; symbol; keymap_var; name; hook; syntax_table_var } in
  Hashtbl.add_exn t_by_symbol ~key:(symbol |> Symbol.name) ~data:t;
  t
;;

let keymap t = Current_buffer.value_exn t.keymap_var
let keymap_var t = t.keymap_var
let syntax_table t = Current_buffer.value_exn t.syntax_table_var

let wrap_existing_with_lazy_keymap name wrapped_at : (module S_with_lazy_keymap) =
  (module struct
    type Name.t += Major_mode

    let major_mode =
      match Hashtbl.find t_by_symbol name with
      | None -> add wrapped_at Major_mode (name |> Symbol.intern)
      | Some t ->
        raise_s
          [%message
            "Already associated with a name."
              (name : string)
              (wrapped_at : Source_code_position.t)
              ~previous_def:(t : t)]
    ;;

    let keymap =
      lazy
        (try keymap major_mode with
         | exn ->
           raise_s
             [%message
               "Major mode's keymap doesn't exist"
                 (name : string)
                 (wrapped_at : Source_code_position.t)
                 (exn : exn)])
    ;;

    let enabled_in_current_buffer () =
      Buffer_local.get major_mode_var (Current_buffer0.get ())
      |> Symbol.name
      |> String.( = ) name
    ;;
  end)
;;

let wrap_existing name wrapped_at : (module S) =
  (module struct
    include (val wrap_existing_with_lazy_keymap name wrapped_at)

    let keymap = force keymap
  end)
;;

let find_or_wrap_existing here symbol =
  match Hashtbl.find t_by_symbol (symbol |> Symbol.name) with
  | Some t -> t
  | None -> add here Name.Undistinguished symbol
;;

module Fundamental = (val wrap_existing_with_lazy_keymap "fundamental-mode" [%here])
module Prog = (val wrap_existing "prog-mode" [%here])
module Special = (val wrap_existing "special-mode" [%here])
module Text = (val wrap_existing "text-mode" [%here])
module Dired = (val wrap_existing_with_lazy_keymap "dired-mode" [%here])
module Tuareg = (val wrap_existing_with_lazy_keymap "tuareg-mode" [%here])
module Makefile = (val wrap_existing_with_lazy_keymap "makefile-mode" [%here])
module Lisp = (val wrap_existing "lisp-mode" [%here])
module Scheme = (val wrap_existing_with_lazy_keymap "scheme-mode" [%here])
module Emacs_lisp = (val wrap_existing "emacs-lisp-mode" [%here])
module Asm = (val wrap_existing_with_lazy_keymap "asm-mode" [%here])
module Python = (val wrap_existing_with_lazy_keymap "python-mode" [%here])

let all_derived_modes = ref []

module For_testing = struct
  let all_derived_modes () = !all_derived_modes |> List.sort ~compare:compare_name
end

let add_auto_mode auto_mode ~symbol =
  let filename_match, delete_suffix_and_recur =
    match (auto_mode : Auto_mode.t) with
    | If_filename_matches regexp -> regexp, false
    | If_filename_matches_then_delete_suffix_and_recur regexp -> regexp, true
  in
  Auto_mode_alist.add
    [ { delete_suffix_and_recur; filename_match; function_ = Some symbol } ]
;;

let define_derived_mode
      (type a)
      ?auto_mode
      symbol
      here
      ~docstring
      ?(define_keys = [])
      ~mode_line
      ?parent
      ?(initialize : ((unit, a) Defun.Returns.t * (unit -> a)) option)
      ()
  =
  let symbol =
    match Symbol.Automatic_migration.migrate ~old:symbol with
    | None -> symbol
    | Some { new_; since } ->
      Defun.define_obsolete_alias symbol here ~alias_of:new_ ~since ();
      List.iter
        [ "abbrev-table"; "hook"; "map"; "syntax-table" ]
        ~f:
          (let old_prefix = Symbol.name symbol in
           let new_prefix = Symbol.name new_ in
           fun suffix ->
             Defvar.define_obsolete_alias
               ([%string "%{old_prefix}-%{suffix}"] |> Symbol.intern)
               here
               ~alias_of:([%string "%{new_prefix}-%{suffix}"] |> Symbol.intern)
               ~since
               ());
      new_
  in
  let docstring = docstring |> String.strip in
  require_nonempty_docstring here ~docstring;
  let initialize_fn =
    match initialize with
    | None -> Defun.lambda_nullary_nil here Fn.id
    | Some (returns, f) -> Defun.lambda_nullary here returns f
  in
  Form.Blocking.eval_i
    (Form.list
       [ Q.define_derived_mode |> Form.symbol
       ; symbol |> Form.symbol
       ; (match parent with
          | None -> Form.nil
          | Some t -> Field.get Fields.symbol t |> Form.symbol)
       ; mode_line |> Form.string
       ; docstring |> Form.string
       ; Form.list
           [ Q.funcall |> Form.symbol; Form.quote (initialize_fn |> Function.to_value) ]
       ]);
  Load_history.add_entry here (Fun symbol);
  List.iter [ "abbrev-table"; "hook"; "map"; "syntax-table" ] ~f:(fun suffix ->
    Load_history.add_entry
      here
      (Var (concat [ symbol |> Symbol.name; "-"; suffix ] |> Symbol.intern)));
  let m = wrap_existing (symbol |> Symbol.name) here in
  let module M = (val m) in
  List.iter define_keys ~f:(fun (keys, symbol) ->
    Keymap.define_key M.keymap (Key_sequence.create_exn keys) (Symbol symbol));
  all_derived_modes := M.major_mode :: !all_derived_modes;
  Option.iter auto_mode ~f:(add_auto_mode ~symbol);
  m
;;

let derived_mode_p = Funcall.Wrap.("derived-mode-p" <: Symbol.t @-> return bool)

let is_derived t ~from =
  Current_buffer0.set_value_temporarily
    Sync
    (major_mode_var |> Buffer_local.var)
    (symbol t)
    ~f:(fun () -> derived_mode_p (symbol from))
;;