Source file text_document.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
open Import

include struct
  open Types
  module DidOpenTextDocumentParams = DidOpenTextDocumentParams
  module DocumentUri = DocumentUri
  module Range = Range
  module TextDocumentItem = TextDocumentItem
  module TextDocumentContentChangeEvent = TextDocumentContentChangeEvent
  module TextEdit = TextEdit
end

type invalid_utf = String_zipper.invalid_utf =
  | Malformed of string
  | Insufficient_input

exception Invalid_utf = String_zipper.Invalid_utf

type t =
  { languageId : string
  ; (* text is stored as utf8 internally no matter what the encoding is *)
    mutable text : string option
  ; uri : DocumentUri.t
  ; version : int
  ; mutable zipper : String_zipper.t
  ; position_encoding : [ `UTF8 | `UTF16 ]
  }

let position_encoding t = t.position_encoding

let make ~position_encoding
    { DidOpenTextDocumentParams.textDocument =
        { TextDocumentItem.languageId; text; uri; version }
    } =
  let zipper = String_zipper.of_string text in
  { text = Some text; position_encoding; zipper; uri; version; languageId }

let documentUri (t : t) = t.uri

let version (t : t) = t.version

let languageId (t : t) = t.languageId

let apply_change encoding sz (change : TextDocumentContentChangeEvent.t) =
  match change.range with
  | None -> String_zipper.of_string change.text
  | Some range ->
    String_zipper.apply_change sz range encoding ~replacement:change.text

let apply_content_changes ?version t changes =
  let zipper =
    List.fold_left ~f:(apply_change t.position_encoding) ~init:t.zipper changes
  in
  let version =
    match version with
    | None -> t.version
    | Some version -> version
  in
  { t with zipper; text = None; version }

let text t =
  match t.text with
  | Some text -> text
  | None ->
    let zipper, text = String_zipper.squash t.zipper in
    t.text <- Some text;
    t.zipper <- zipper;
    text

module Edit_map = Map.Make (Position)

let add_edit map (change : TextEdit.t) =
  (* TODO check non overlapping property *)
  Edit_map.update map ~key:change.range.start ~f:(function
      | None -> Some [ change ]
      | Some changes -> Some (change :: changes))

let apply_changes zipper encoding changes =
  let simplified = List.fold_left changes ~init:Edit_map.empty ~f:add_edit in
  let b = Buffer.create 0 in
  let pos = ref Position.zero in
  let zipper = String_zipper.goto_position zipper !pos encoding in
  let zipper = ref zipper in
  Edit_map.iter simplified ~f:(fun ~key:start ~data ->
      (* guaranteed by the non overlapping property we aren't yet checking *)
      assert (Position.compare start !pos >= 0);
      zipper := String_zipper.goto_position !zipper !pos encoding;
      let zipper' = String_zipper.goto_position !zipper start encoding in
      String_zipper.add_buffer_between b !zipper zipper';
      zipper := zipper';
      List.rev data
      |> List.iter ~f:(fun { TextEdit.newText; range } ->
             assert (Position.compare range.end_ !pos >= 0);
             pos := range.end_;
             Buffer.add_string b newText));
  let zipper = String_zipper.goto_position !zipper !pos encoding in
  let zipper' = String_zipper.goto_end zipper in
  String_zipper.add_buffer_between b zipper zipper';
  Buffer.contents b

let set_version t ~version = { t with version }

let apply_text_document_edits t (edits : TextEdit.t list) =
  let text = apply_changes t.zipper t.position_encoding edits in
  let zipper = String_zipper.of_string text in
  { t with text = Some text; zipper }

let absolute_position t pos =
  String_zipper.goto_position t.zipper pos t.position_encoding
  |> String_zipper.offset

let absolute_range t (range : Range.t) =
  let zipper =
    String_zipper.goto_position t.zipper range.start t.position_encoding
  in
  let start = String_zipper.offset zipper in
  let zipper =
    String_zipper.goto_position zipper range.end_ t.position_encoding
  in
  let stop = String_zipper.offset zipper in
  (start, stop)