author | Thomas Lindae <thomas.lindae@in.tum.de> |
Sun, 30 Jun 2024 15:32:45 +0200 | |
changeset 81063 | a5d42b37331f |
parent 81061 | fe9ae6b67c29 |
child 81084 | 96eb20106a34 |
permissions | -rw-r--r-- |
72761 | 1 |
/* Title: Tools/VSCode/src/vscode_model.scala |
64605 | 2 |
Author: Makarius |
3 |
||
72761 | 4 |
VSCode document model for line-oriented text. |
64605 | 5 |
*/ |
6 |
||
7 |
package isabelle.vscode |
|
8 |
||
9 |
||
10 |
import isabelle._ |
|
11 |
||
64710 | 12 |
import java.io.{File => JFile} |
13 |
||
64605 | 14 |
|
75393 | 15 |
object VSCode_Model { |
65095 | 16 |
/* decorations */ |
17 |
||
75393 | 18 |
object Decoration { |
65140 | 19 |
def empty(typ: String): Decoration = Decoration(typ, Nil) |
20 |
||
21 |
def ranges(typ: String, ranges: List[Text.Range]): Decoration = |
|
22 |
Decoration(typ, ranges.map(Text.Info(_, List.empty[XML.Body]))) |
|
23 |
} |
|
65105 | 24 |
sealed case class Decoration(typ: String, content: List[Text.Info[List[XML.Body]]]) |
65095 | 25 |
|
26 |
||
27 |
/* content */ |
|
28 |
||
76781
d9f48960bf23
clarified signature: more position information via node_name;
wenzelm
parents:
76778
diff
changeset
|
29 |
sealed case class Content(node_name: Document.Node.Name, doc: Line.Document) { |
65161 | 30 |
override def toString: String = doc.toString |
65197 | 31 |
def text_length: Text.Offset = doc.text_length |
81061
fe9ae6b67c29
clarified PIDE/line range conversions;
Thomas Lindae <thomas.lindae@in.tum.de>
parents:
81043
diff
changeset
|
32 |
def text_range: Text.Range = doc.full_range |
64830 | 33 |
def text: String = doc.text |
64833 | 34 |
|
76307
072e6c0a2373
proper Symbol.encode (following fd1efd6dd385), e.g. relevant for 'ML_file' with symbols like \<^here>;
wenzelm
parents:
75393
diff
changeset
|
35 |
lazy val bytes: Bytes = Bytes(Symbol.encode(text)) |
64830 | 36 |
lazy val chunk: Symbol.Text_Chunk = Symbol.Text_Chunk(text) |
76792
23f433294173
support for generic File_Format.parse_data, with persistent result in document model;
wenzelm
parents:
76781
diff
changeset
|
37 |
lazy val data: AnyRef = File_Format.registry.parse_data(node_name, text) |
66676
39db5bb7eb0a
recode Unicode text on the spot, e.g. from copy-paste of output;
wenzelm
parents:
66674
diff
changeset
|
38 |
|
72761 | 39 |
def recode_symbols: List[LSP.TextEdit] = |
66676
39db5bb7eb0a
recode Unicode text on the spot, e.g. from copy-paste of output;
wenzelm
parents:
66674
diff
changeset
|
40 |
(for { |
39db5bb7eb0a
recode Unicode text on the spot, e.g. from copy-paste of output;
wenzelm
parents:
66674
diff
changeset
|
41 |
(line, l) <- doc.lines.iterator.zipWithIndex |
39db5bb7eb0a
recode Unicode text on the spot, e.g. from copy-paste of output;
wenzelm
parents:
66674
diff
changeset
|
42 |
text1 = Symbol.encode(line.text) |
39db5bb7eb0a
recode Unicode text on the spot, e.g. from copy-paste of output;
wenzelm
parents:
66674
diff
changeset
|
43 |
if (line.text != text1) |
39db5bb7eb0a
recode Unicode text on the spot, e.g. from copy-paste of output;
wenzelm
parents:
66674
diff
changeset
|
44 |
} yield { |
39db5bb7eb0a
recode Unicode text on the spot, e.g. from copy-paste of output;
wenzelm
parents:
66674
diff
changeset
|
45 |
val range = Line.Range(Line.Position(l), Line.Position(l, line.text.length)) |
72761 | 46 |
LSP.TextEdit(range, text1) |
66676
39db5bb7eb0a
recode Unicode text on the spot, e.g. from copy-paste of output;
wenzelm
parents:
66674
diff
changeset
|
47 |
}).toList |
64830 | 48 |
} |
49 |
||
75393 | 50 |
def init( |
51 |
session: Session, |
|
52 |
editor: Language_Server.Editor, |
|
53 |
node_name: Document.Node.Name |
|
54 |
): VSCode_Model = { |
|
76781
d9f48960bf23
clarified signature: more position information via node_name;
wenzelm
parents:
76778
diff
changeset
|
55 |
val content = Content(node_name, Line.Document.empty) |
d9f48960bf23
clarified signature: more position information via node_name;
wenzelm
parents:
76778
diff
changeset
|
56 |
val is_theory = File_Format.registry.is_theory(node_name) |
d9f48960bf23
clarified signature: more position information via node_name;
wenzelm
parents:
76778
diff
changeset
|
57 |
VSCode_Model(session, editor, content, node_required = is_theory) |
72761 | 58 |
} |
64707 | 59 |
} |
60 |
||
72761 | 61 |
sealed case class VSCode_Model( |
64707 | 62 |
session: Session, |
72761 | 63 |
editor: Language_Server.Editor, |
64 |
content: VSCode_Model.Content, |
|
66674 | 65 |
version: Option[Long] = None, |
64726 | 66 |
external_file: Boolean = false, |
76715
bf5ff407f32f
clarified state of document model vs. document editor selection (again, see also a9d52d02bd83);
wenzelm
parents:
76703
diff
changeset
|
67 |
node_required: Boolean = false, |
76702 | 68 |
last_perspective: Document.Node.Perspective_Text.T = Document.Node.Perspective_Text.empty, |
64816 | 69 |
pending_edits: List[Text.Edit] = Nil, |
65095 | 70 |
published_diagnostics: List[Text.Info[Command.Results]] = Nil, |
75393 | 71 |
published_decorations: List[VSCode_Model.Decoration] = Nil |
72 |
) extends Document.Model { |
|
66114 | 73 |
model => |
74 |
||
76765
c654103e9c9d
more robust Document.Pending_Edits: cover all nodes simulataneously, and thus support proper Snapshot.switch;
wenzelm
parents:
76761
diff
changeset
|
75 |
|
66674 | 76 |
/* content */ |
66114 | 77 |
|
76781
d9f48960bf23
clarified signature: more position information via node_name;
wenzelm
parents:
76778
diff
changeset
|
78 |
def node_name: Document.Node.Name = content.node_name |
d9f48960bf23
clarified signature: more position information via node_name;
wenzelm
parents:
76778
diff
changeset
|
79 |
|
67014 | 80 |
def get_text(range: Text.Range): Option[String] = content.doc.get_text(range) |
66114 | 81 |
|
72761 | 82 |
def set_version(new_version: Long): VSCode_Model = copy(version = Some(new_version)) |
66674 | 83 |
|
66114 | 84 |
|
64710 | 85 |
/* external file */ |
86 |
||
72761 | 87 |
def external(b: Boolean): VSCode_Model = copy(external_file = b) |
64727
13e37567a0d6
automatically resolve dependencies from document models and file-system;
wenzelm
parents:
64726
diff
changeset
|
88 |
|
64800 | 89 |
def node_visible: Boolean = !external_file |
90 |
||
64710 | 91 |
|
64690 | 92 |
/* header */ |
93 |
||
64702 | 94 |
def node_header: Document.Node.Header = |
64673
b5965890e54d
more uniform treatment of file name vs. theory name and special header;
wenzelm
parents:
64672
diff
changeset
|
95 |
resources.special_header(node_name) getOrElse |
72772 | 96 |
resources.check_thy(node_name, Scan.char_reader(content.text)) |
64605 | 97 |
|
98 |
||
64707 | 99 |
/* perspective */ |
100 |
||
75393 | 101 |
def node_perspective( |
102 |
doc_blobs: Document.Blobs, |
|
103 |
caret: Option[Line.Position] |
|
76702 | 104 |
): (Boolean, Document.Node.Perspective_Text.T) = { |
64800 | 105 |
if (is_theory) { |
76765
c654103e9c9d
more robust Document.Pending_Edits: cover all nodes simulataneously, and thus support proper Snapshot.switch;
wenzelm
parents:
76761
diff
changeset
|
106 |
val snapshot = resources.snapshot(model) |
64800 | 107 |
|
76715
bf5ff407f32f
clarified state of document model vs. document editor selection (again, see also a9d52d02bd83);
wenzelm
parents:
76703
diff
changeset
|
108 |
val required = node_required || editor.document_node_required(node_name) |
bf5ff407f32f
clarified state of document model vs. document editor selection (again, see also a9d52d02bd83);
wenzelm
parents:
76703
diff
changeset
|
109 |
|
65926
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
110 |
val caret_perspective = resources.options.int("vscode_caret_perspective") max 0 |
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
111 |
val caret_range = |
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
112 |
if (caret_perspective != 0) { |
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
113 |
caret match { |
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
114 |
case Some(pos) => |
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
115 |
val doc = content.doc |
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
116 |
val pos1 = Line.Position((pos.line - caret_perspective) max 0) |
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
117 |
val pos2 = Line.Position((pos.line + caret_perspective + 1) min doc.lines.length) |
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
118 |
Text.Range(doc.offset(pos1).get, doc.offset(pos2).get) |
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
119 |
case None => Text.Range.offside |
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
120 |
} |
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
121 |
} |
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
122 |
else if (node_visible) content.text_range |
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
123 |
else Text.Range.offside |
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
124 |
|
64800 | 125 |
val text_perspective = |
65926
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
126 |
if (snapshot.commands_loading_ranges(resources.visible_node(_)).nonEmpty) |
64800 | 127 |
Text.Perspective.full |
65926
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
128 |
else |
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
129 |
content.text_range.try_restrict(caret_range) match { |
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
130 |
case Some(range) => Text.Perspective(List(range)) |
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
131 |
case None => Text.Perspective.empty |
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
132 |
} |
64723 | 133 |
|
66100 | 134 |
val overlays = editor.node_overlays(node_name) |
135 |
||
64800 | 136 |
(snapshot.node.load_commands_changed(doc_blobs), |
76715
bf5ff407f32f
clarified state of document model vs. document editor selection (again, see also a9d52d02bd83);
wenzelm
parents:
76703
diff
changeset
|
137 |
Document.Node.Perspective(required, text_perspective, overlays)) |
64800 | 138 |
} |
76702 | 139 |
else (false, Document.Node.Perspective_Text.empty) |
64800 | 140 |
} |
141 |
||
64707 | 142 |
|
64800 | 143 |
/* blob */ |
144 |
||
76904
e27d097d7d15
tuned signature: avoid confusion with Document.Node.Blob and Command.Blob;
wenzelm
parents:
76792
diff
changeset
|
145 |
def get_blob: Option[Document.Blobs.Item] = |
64800 | 146 |
if (is_theory) None |
76904
e27d097d7d15
tuned signature: avoid confusion with Document.Node.Blob and Command.Blob;
wenzelm
parents:
76792
diff
changeset
|
147 |
else Some(Document.Blobs.Item(content.bytes, content.text, content.chunk, pending_edits.nonEmpty)) |
64707 | 148 |
|
149 |
||
76792
23f433294173
support for generic File_Format.parse_data, with persistent result in document model;
wenzelm
parents:
76781
diff
changeset
|
150 |
/* data */ |
66150 | 151 |
|
76792
23f433294173
support for generic File_Format.parse_data, with persistent result in document model;
wenzelm
parents:
76781
diff
changeset
|
152 |
def untyped_data: AnyRef = model.content.data |
66150 | 153 |
|
154 |
||
64605 | 155 |
/* edits */ |
156 |
||
75393 | 157 |
def change_text(text: String, range: Option[Line.Range] = None): Option[VSCode_Model] = { |
65160 | 158 |
val insert = Line.normalize(text) |
159 |
range match { |
|
160 |
case None => |
|
161 |
Text.Edit.replace(0, content.text, insert) match { |
|
162 |
case Nil => None |
|
163 |
case edits => |
|
76781
d9f48960bf23
clarified signature: more position information via node_name;
wenzelm
parents:
76778
diff
changeset
|
164 |
val content1 = VSCode_Model.Content(node_name, Line.Document(insert)) |
65160 | 165 |
Some(copy(content = content1, pending_edits = pending_edits ::: edits)) |
166 |
} |
|
167 |
case Some(remove) => |
|
168 |
content.doc.change(remove, insert) match { |
|
169 |
case None => error("Failed to apply document change: " + remove) |
|
170 |
case Some((Nil, _)) => None |
|
171 |
case Some((edits, doc1)) => |
|
76781
d9f48960bf23
clarified signature: more position information via node_name;
wenzelm
parents:
76778
diff
changeset
|
172 |
val content1 = VSCode_Model.Content(node_name, doc1) |
65160 | 173 |
Some(copy(content = content1, pending_edits = pending_edits ::: edits)) |
174 |
} |
|
64709 | 175 |
} |
176 |
} |
|
177 |
||
66676
39db5bb7eb0a
recode Unicode text on the spot, e.g. from copy-paste of output;
wenzelm
parents:
66674
diff
changeset
|
178 |
def flush_edits( |
39db5bb7eb0a
recode Unicode text on the spot, e.g. from copy-paste of output;
wenzelm
parents:
66674
diff
changeset
|
179 |
doc_blobs: Document.Blobs, |
39db5bb7eb0a
recode Unicode text on the spot, e.g. from copy-paste of output;
wenzelm
parents:
66674
diff
changeset
|
180 |
file: JFile, |
75393 | 181 |
caret: Option[Line.Position] |
81063
a5d42b37331f
lsp: removed code that is never run;
Thomas Lindae <thomas.lindae@in.tum.de>
parents:
81061
diff
changeset
|
182 |
): Option[(List[Document.Edit_Text], VSCode_Model)] = { |
65926
0f7821a07aa9
restricted perspective depending on the caret -- important for reactivity when editing big files;
wenzelm
parents:
65923
diff
changeset
|
183 |
val (reparse, perspective) = node_perspective(doc_blobs, caret) |
81063
a5d42b37331f
lsp: removed code that is never run;
Thomas Lindae <thomas.lindae@in.tum.de>
parents:
81061
diff
changeset
|
184 |
if (reparse || pending_edits.nonEmpty || last_perspective != perspective) { |
66676
39db5bb7eb0a
recode Unicode text on the spot, e.g. from copy-paste of output;
wenzelm
parents:
66674
diff
changeset
|
185 |
val prover_edits = node_edits(node_header, pending_edits, perspective) |
81063
a5d42b37331f
lsp: removed code that is never run;
Thomas Lindae <thomas.lindae@in.tum.de>
parents:
81061
diff
changeset
|
186 |
val edits = (prover_edits) |
a5d42b37331f
lsp: removed code that is never run;
Thomas Lindae <thomas.lindae@in.tum.de>
parents:
81061
diff
changeset
|
187 |
Some(edits, copy(pending_edits = Nil, last_perspective = perspective)) |
64605 | 188 |
} |
64707 | 189 |
else None |
190 |
} |
|
64605 | 191 |
|
192 |
||
65095 | 193 |
/* publish annotations */ |
64679
b2bf280b7e13
more uniform treatment of input/output wrt. client;
wenzelm
parents:
64673
diff
changeset
|
194 |
|
75393 | 195 |
def publish( |
196 |
rendering: VSCode_Rendering |
|
197 |
): (Option[List[Text.Info[Command.Results]]], Option[List[VSCode_Model.Decoration]], VSCode_Model) = { |
|
81043
2174ec5f0d0c
lsp: added decoration_request notification;
Thomas Lindae <thomas.lindae@in.tum.de>
parents:
76904
diff
changeset
|
198 |
val (diagnostics, decorations, model) = publish_full(rendering) |
2174ec5f0d0c
lsp: added decoration_request notification;
Thomas Lindae <thomas.lindae@in.tum.de>
parents:
76904
diff
changeset
|
199 |
|
65115 | 200 |
val changed_diagnostics = |
201 |
if (diagnostics == published_diagnostics) None else Some(diagnostics) |
|
202 |
val changed_decorations = |
|
203 |
if (decorations == published_decorations) None |
|
65120
db6cc23fcf33
proper reset of published decorations: initial value is Nil, afterwards it is a list of canonical length and order;
wenzelm
parents:
65115
diff
changeset
|
204 |
else if (published_decorations.isEmpty) Some(decorations) |
65115 | 205 |
else Some(for { (a, b) <- decorations zip published_decorations if a != b } yield a) |
206 |
||
81043
2174ec5f0d0c
lsp: added decoration_request notification;
Thomas Lindae <thomas.lindae@in.tum.de>
parents:
76904
diff
changeset
|
207 |
(changed_diagnostics, changed_decorations, model) |
2174ec5f0d0c
lsp: added decoration_request notification;
Thomas Lindae <thomas.lindae@in.tum.de>
parents:
76904
diff
changeset
|
208 |
} |
2174ec5f0d0c
lsp: added decoration_request notification;
Thomas Lindae <thomas.lindae@in.tum.de>
parents:
76904
diff
changeset
|
209 |
|
2174ec5f0d0c
lsp: added decoration_request notification;
Thomas Lindae <thomas.lindae@in.tum.de>
parents:
76904
diff
changeset
|
210 |
def publish_full( |
2174ec5f0d0c
lsp: added decoration_request notification;
Thomas Lindae <thomas.lindae@in.tum.de>
parents:
76904
diff
changeset
|
211 |
rendering: VSCode_Rendering |
2174ec5f0d0c
lsp: added decoration_request notification;
Thomas Lindae <thomas.lindae@in.tum.de>
parents:
76904
diff
changeset
|
212 |
): (List[Text.Info[Command.Results]],List[VSCode_Model.Decoration], VSCode_Model) = { |
2174ec5f0d0c
lsp: added decoration_request notification;
Thomas Lindae <thomas.lindae@in.tum.de>
parents:
76904
diff
changeset
|
213 |
val diagnostics = rendering.diagnostics |
2174ec5f0d0c
lsp: added decoration_request notification;
Thomas Lindae <thomas.lindae@in.tum.de>
parents:
76904
diff
changeset
|
214 |
val decorations = |
2174ec5f0d0c
lsp: added decoration_request notification;
Thomas Lindae <thomas.lindae@in.tum.de>
parents:
76904
diff
changeset
|
215 |
if (node_visible) rendering.decorations |
2174ec5f0d0c
lsp: added decoration_request notification;
Thomas Lindae <thomas.lindae@in.tum.de>
parents:
76904
diff
changeset
|
216 |
else { for (deco <- published_decorations) yield VSCode_Model.Decoration.empty(deco.typ) } |
2174ec5f0d0c
lsp: added decoration_request notification;
Thomas Lindae <thomas.lindae@in.tum.de>
parents:
76904
diff
changeset
|
217 |
|
2174ec5f0d0c
lsp: added decoration_request notification;
Thomas Lindae <thomas.lindae@in.tum.de>
parents:
76904
diff
changeset
|
218 |
(diagnostics, decorations, |
65115 | 219 |
copy(published_diagnostics = diagnostics, published_decorations = decorations)) |
64679
b2bf280b7e13
more uniform treatment of input/output wrt. client;
wenzelm
parents:
64673
diff
changeset
|
220 |
} |
b2bf280b7e13
more uniform treatment of input/output wrt. client;
wenzelm
parents:
64673
diff
changeset
|
221 |
|
b2bf280b7e13
more uniform treatment of input/output wrt. client;
wenzelm
parents:
64673
diff
changeset
|
222 |
|
64725 | 223 |
/* prover session */ |
64702 | 224 |
|
225 |
def resources: VSCode_Resources = session.resources.asInstanceOf[VSCode_Resources] |
|
64605 | 226 |
|
64816 | 227 |
def is_stable: Boolean = pending_edits.isEmpty |
66054
fb0eea226a4d
more uniform syntax_completion + semantic_completion;
wenzelm
parents:
65926
diff
changeset
|
228 |
|
fb0eea226a4d
more uniform syntax_completion + semantic_completion;
wenzelm
parents:
65926
diff
changeset
|
229 |
|
fb0eea226a4d
more uniform syntax_completion + semantic_completion;
wenzelm
parents:
65926
diff
changeset
|
230 |
/* syntax */ |
fb0eea226a4d
more uniform syntax_completion + semantic_completion;
wenzelm
parents:
65926
diff
changeset
|
231 |
|
fb0eea226a4d
more uniform syntax_completion + semantic_completion;
wenzelm
parents:
65926
diff
changeset
|
232 |
def syntax(): Outer_Syntax = |
67004
af72fa58f71b
clarified lazy Completion within Outer_Syntax: measurable speedup of Sessions.deps;
wenzelm
parents:
66984
diff
changeset
|
233 |
if (is_theory) session.recent_syntax(node_name) else Outer_Syntax.empty |
64605 | 234 |
} |