|
1 /* Title: Tools/VSCode/src/vscode_model.scala |
|
2 Author: Makarius |
|
3 |
|
4 VSCode document model for line-oriented text. |
|
5 */ |
|
6 |
|
7 package isabelle.vscode |
|
8 |
|
9 |
|
10 import isabelle._ |
|
11 |
|
12 import java.io.{File => JFile} |
|
13 |
|
14 |
|
15 object VSCode_Model |
|
16 { |
|
17 /* decorations */ |
|
18 |
|
19 object Decoration |
|
20 { |
|
21 def empty(typ: String): Decoration = Decoration(typ, Nil) |
|
22 |
|
23 def ranges(typ: String, ranges: List[Text.Range]): Decoration = |
|
24 Decoration(typ, ranges.map(Text.Info(_, List.empty[XML.Body]))) |
|
25 } |
|
26 sealed case class Decoration(typ: String, content: List[Text.Info[List[XML.Body]]]) |
|
27 |
|
28 |
|
29 /* content */ |
|
30 |
|
31 object Content |
|
32 { |
|
33 val empty: Content = Content(Line.Document.empty) |
|
34 } |
|
35 |
|
36 sealed case class Content(doc: Line.Document) |
|
37 { |
|
38 override def toString: String = doc.toString |
|
39 def text_length: Text.Offset = doc.text_length |
|
40 def text_range: Text.Range = doc.text_range |
|
41 def text: String = doc.text |
|
42 |
|
43 lazy val bytes: Bytes = Bytes(text) |
|
44 lazy val chunk: Symbol.Text_Chunk = Symbol.Text_Chunk(text) |
|
45 lazy val bibtex_entries: List[Text.Info[String]] = |
|
46 try { Bibtex.entries(text) } |
|
47 catch { case ERROR(_) => Nil } |
|
48 |
|
49 def recode_symbols: List[LSP.TextEdit] = |
|
50 (for { |
|
51 (line, l) <- doc.lines.iterator.zipWithIndex |
|
52 text1 = Symbol.encode(line.text) |
|
53 if (line.text != text1) |
|
54 } yield { |
|
55 val range = Line.Range(Line.Position(l), Line.Position(l, line.text.length)) |
|
56 LSP.TextEdit(range, text1) |
|
57 }).toList |
|
58 } |
|
59 |
|
60 def init(session: Session, editor: Language_Server.Editor, node_name: Document.Node.Name) |
|
61 : VSCode_Model = |
|
62 { |
|
63 VSCode_Model(session, editor, node_name, Content.empty, |
|
64 node_required = File_Format.registry.is_theory(node_name)) |
|
65 } |
|
66 } |
|
67 |
|
68 sealed case class VSCode_Model( |
|
69 session: Session, |
|
70 editor: Language_Server.Editor, |
|
71 node_name: Document.Node.Name, |
|
72 content: VSCode_Model.Content, |
|
73 version: Option[Long] = None, |
|
74 external_file: Boolean = false, |
|
75 node_required: Boolean = false, |
|
76 last_perspective: Document.Node.Perspective_Text = Document.Node.no_perspective_text, |
|
77 pending_edits: List[Text.Edit] = Nil, |
|
78 published_diagnostics: List[Text.Info[Command.Results]] = Nil, |
|
79 published_decorations: List[VSCode_Model.Decoration] = Nil) extends Document.Model |
|
80 { |
|
81 model => |
|
82 |
|
83 |
|
84 /* content */ |
|
85 |
|
86 def get_text(range: Text.Range): Option[String] = content.doc.get_text(range) |
|
87 |
|
88 def set_version(new_version: Long): VSCode_Model = copy(version = Some(new_version)) |
|
89 |
|
90 |
|
91 /* external file */ |
|
92 |
|
93 def external(b: Boolean): VSCode_Model = copy(external_file = b) |
|
94 |
|
95 def node_visible: Boolean = !external_file |
|
96 |
|
97 |
|
98 /* header */ |
|
99 |
|
100 def node_header: Document.Node.Header = |
|
101 resources.special_header(node_name) getOrElse |
|
102 resources.check_thy_reader(node_name, Scan.char_reader(content.text)) |
|
103 |
|
104 |
|
105 /* perspective */ |
|
106 |
|
107 def node_perspective(doc_blobs: Document.Blobs, caret: Option[Line.Position]) |
|
108 : (Boolean, Document.Node.Perspective_Text) = |
|
109 { |
|
110 if (is_theory) { |
|
111 val snapshot = model.snapshot() |
|
112 |
|
113 val caret_perspective = resources.options.int("vscode_caret_perspective") max 0 |
|
114 val caret_range = |
|
115 if (caret_perspective != 0) { |
|
116 caret match { |
|
117 case Some(pos) => |
|
118 val doc = content.doc |
|
119 val pos1 = Line.Position((pos.line - caret_perspective) max 0) |
|
120 val pos2 = Line.Position((pos.line + caret_perspective + 1) min doc.lines.length) |
|
121 Text.Range(doc.offset(pos1).get, doc.offset(pos2).get) |
|
122 case None => Text.Range.offside |
|
123 } |
|
124 } |
|
125 else if (node_visible) content.text_range |
|
126 else Text.Range.offside |
|
127 |
|
128 val text_perspective = |
|
129 if (snapshot.commands_loading_ranges(resources.visible_node(_)).nonEmpty) |
|
130 Text.Perspective.full |
|
131 else |
|
132 content.text_range.try_restrict(caret_range) match { |
|
133 case Some(range) => Text.Perspective(List(range)) |
|
134 case None => Text.Perspective.empty |
|
135 } |
|
136 |
|
137 val overlays = editor.node_overlays(node_name) |
|
138 |
|
139 (snapshot.node.load_commands_changed(doc_blobs), |
|
140 Document.Node.Perspective(node_required, text_perspective, overlays)) |
|
141 } |
|
142 else (false, Document.Node.no_perspective_text) |
|
143 } |
|
144 |
|
145 |
|
146 /* blob */ |
|
147 |
|
148 def get_blob: Option[Document.Blob] = |
|
149 if (is_theory) None |
|
150 else Some(Document.Blob(content.bytes, content.text, content.chunk, pending_edits.nonEmpty)) |
|
151 |
|
152 |
|
153 /* bibtex entries */ |
|
154 |
|
155 def bibtex_entries: List[Text.Info[String]] = |
|
156 model.content.bibtex_entries |
|
157 |
|
158 |
|
159 /* edits */ |
|
160 |
|
161 def change_text(text: String, range: Option[Line.Range] = None): Option[VSCode_Model] = |
|
162 { |
|
163 val insert = Line.normalize(text) |
|
164 range match { |
|
165 case None => |
|
166 Text.Edit.replace(0, content.text, insert) match { |
|
167 case Nil => None |
|
168 case edits => |
|
169 val content1 = VSCode_Model.Content(Line.Document(insert)) |
|
170 Some(copy(content = content1, pending_edits = pending_edits ::: edits)) |
|
171 } |
|
172 case Some(remove) => |
|
173 content.doc.change(remove, insert) match { |
|
174 case None => error("Failed to apply document change: " + remove) |
|
175 case Some((Nil, _)) => None |
|
176 case Some((edits, doc1)) => |
|
177 val content1 = VSCode_Model.Content(doc1) |
|
178 Some(copy(content = content1, pending_edits = pending_edits ::: edits)) |
|
179 } |
|
180 } |
|
181 } |
|
182 |
|
183 def flush_edits( |
|
184 unicode_symbols: Boolean, |
|
185 doc_blobs: Document.Blobs, |
|
186 file: JFile, |
|
187 caret: Option[Line.Position]) |
|
188 : Option[((List[LSP.TextDocumentEdit], List[Document.Edit_Text]), VSCode_Model)] = |
|
189 { |
|
190 val workspace_edits = |
|
191 if (unicode_symbols && version.isDefined) { |
|
192 val edits = content.recode_symbols |
|
193 if (edits.nonEmpty) List(LSP.TextDocumentEdit(file, version.get, edits)) |
|
194 else Nil |
|
195 } |
|
196 else Nil |
|
197 |
|
198 val (reparse, perspective) = node_perspective(doc_blobs, caret) |
|
199 if (reparse || pending_edits.nonEmpty || last_perspective != perspective || |
|
200 workspace_edits.nonEmpty) |
|
201 { |
|
202 val prover_edits = node_edits(node_header, pending_edits, perspective) |
|
203 val edits = (workspace_edits, prover_edits) |
|
204 Some((edits, copy(pending_edits = Nil, last_perspective = perspective))) |
|
205 } |
|
206 else None |
|
207 } |
|
208 |
|
209 |
|
210 /* publish annotations */ |
|
211 |
|
212 def publish(rendering: VSCode_Rendering): |
|
213 (Option[List[Text.Info[Command.Results]]], Option[List[VSCode_Model.Decoration]], VSCode_Model) = |
|
214 { |
|
215 val diagnostics = rendering.diagnostics |
|
216 val decorations = |
|
217 if (node_visible) rendering.decorations |
|
218 else { for (deco <- published_decorations) yield VSCode_Model.Decoration.empty(deco.typ) } |
|
219 |
|
220 val changed_diagnostics = |
|
221 if (diagnostics == published_diagnostics) None else Some(diagnostics) |
|
222 val changed_decorations = |
|
223 if (decorations == published_decorations) None |
|
224 else if (published_decorations.isEmpty) Some(decorations) |
|
225 else Some(for { (a, b) <- decorations zip published_decorations if a != b } yield a) |
|
226 |
|
227 (changed_diagnostics, changed_decorations, |
|
228 copy(published_diagnostics = diagnostics, published_decorations = decorations)) |
|
229 } |
|
230 |
|
231 |
|
232 /* prover session */ |
|
233 |
|
234 def resources: VSCode_Resources = session.resources.asInstanceOf[VSCode_Resources] |
|
235 |
|
236 def is_stable: Boolean = pending_edits.isEmpty |
|
237 def snapshot(): Document.Snapshot = session.snapshot(node_name, pending_edits) |
|
238 |
|
239 def rendering(snapshot: Document.Snapshot): VSCode_Rendering = |
|
240 new VSCode_Rendering(snapshot, model) |
|
241 def rendering(): VSCode_Rendering = rendering(snapshot()) |
|
242 |
|
243 |
|
244 /* syntax */ |
|
245 |
|
246 def syntax(): Outer_Syntax = |
|
247 if (is_theory) session.recent_syntax(node_name) else Outer_Syntax.empty |
|
248 } |