-
-
Notifications
You must be signed in to change notification settings - Fork 195
Expand file tree
/
Copy pathLiveDocument.js
More file actions
339 lines (298 loc) · 11.7 KB
/
Copy pathLiveDocument.js
File metadata and controls
339 lines (298 loc) · 11.7 KB
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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
/*
* GNU AGPL-3.0 License
*
* Copyright (c) 2021 - present core.ai . All rights reserved.
* Original work Copyright (c) 2014 - 2021 Adobe Systems Incorporated. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
* for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://opensource.org/licenses/AGPL-3.0.
*
*/
define(function (require, exports, module) {
const CONSTANTS = require("LiveDevelopment/LivePreviewConstants"),
EditorManager = require("editor/EditorManager"),
EventDispatcher = require("utils/EventDispatcher"),
PreferencesManager = require("preferences/PreferencesManager"),
_ = require("thirdparty/lodash");
/**
* @const
* CSS class to use for live preview errors.
* @type {string}
*/
var SYNC_ERROR_CLASS = "live-preview-sync-error";
function _simpleHash(str) {
let hash = 5381;
for (let i = 0; i < str.length; ) {
// eslint-disable-next-line no-bitwise
hash = (hash * 33) ^ str.charCodeAt(i++);
}
// eslint-disable-next-line no-bitwise
return hash >>> 0;
}
/**
* @constructor
* Base class for managing the connection between a live editor and the browser. Provides functions
* for subclasses to highlight relevant nodes in the browser, and to mark errors in the editor.
*
* Raises these events:
* "connect" - when a browser connects from the URL that maps to this live document. Passes the
* URL as a parameter.
* "errorStatusChanged" - when the status of errors that might prevent live edits from
* working (e.g. HTML syntax errors) has changed. Passes a boolean that's true if there
* are errors, false otherwise.
*
* @param {LiveDevProtocol} protocol The protocol to use for communicating with the browser.
* @param {function(string): string} urlResolver A function that, given a path on disk, should return
* the URL that Live Development serves that path at.
* @param {Document} doc The Brackets document that this live document is connected to.
* @param {?Editor} editor If specified, a particular editor that this live document is managing.
* If not specified initially, the LiveDocument will connect to the editor for the given document
* when it next becomes the active editor.
*/
function LiveDocument(protocol, urlResolver, doc, editor, roots) {
this.protocol = protocol;
this.urlResolver = urlResolver;
this.doc = doc;
this.roots = roots || [];
this._onActiveEditorChange = this._onActiveEditorChange.bind(this);
this._onCursorActivity = this._onCursorActivity.bind(this);
// we cant use file paths for event registration - paths may have spaces(treated as an event list separator)
this.fileHashForEvents = _simpleHash(this.doc.file.fullPath);
EditorManager.off(`activeEditorChange.LiveDocument-${this.fileHashForEvents}`);
EditorManager.on(`activeEditorChange.LiveDocument-${this.fileHashForEvents}`, this._onActiveEditorChange);
if (editor) {
// Attach now
this._attachToEditor(editor);
}
}
EventDispatcher.makeEventDispatcher(LiveDocument.prototype);
/**
* Closes the live document, terminating its connection to the browser.
*/
LiveDocument.prototype.close = function () {
EditorManager.off(`activeEditorChange.LiveDocument-${this.fileHashForEvents}`);
this._clearErrorDisplay();
this._detachFromEditor();
};
/**
* Returns true if document edits appear live in the connected browser.
* Should be overridden by subclasses.
* @return {boolean}
*/
LiveDocument.prototype.isLiveEditingEnabled = function () {
return false;
};
/**
* Called to turn instrumentation on or off for this file. Triggered by being
* requested from the browser. Should be implemented by subclasses if instrumentation
* is necessary for the subclass's document type.
* TODO: this doesn't seem necessary...if we're a live document, we should
* always have instrumentation on anyway.
* @param {boolean} enabled
*/
LiveDocument.prototype.setInstrumentationEnabled = function (enabled) {
// Does nothing in base class.
};
/**
* Returns the instrumented version of the file. By default, just returns
* the document text. Should be overridden by subclasses for cases if instrumentation
* is necessary for the subclass's document type.
* @return {{body: string}} document body
*/
LiveDocument.prototype.getResponseData = function (enabled) {
return {
body: this.doc.getText()
};
};
/**
* @private
* Handles when the active editor changes, attaching to the new editor if it's for the current document.
* @param {$.Event} event
* @param {?Editor} newActive
* @param {?Editor} oldActive
*/
LiveDocument.prototype._onActiveEditorChange = function (event, newActive, oldActive) {
this._detachFromEditor();
if (newActive && newActive.document.file.fullPath === this.doc.file.fullPath) {
this._attachToEditor(newActive);
}
};
/**
* @private
* Attaches to an editor for our associated document when it becomes active.
* @param {Editor} editor
*/
LiveDocument.prototype._attachToEditor = function (editor) {
this.editor = editor;
if (this.editor) {
this.setInstrumentationEnabled(true, true);
this.editor.off("cursorActivity", this._onCursorActivity);
this.editor.on("cursorActivity", this._onCursorActivity);
this.updateHighlight();
}
};
/**
* @private
* Detaches from the current editor.
*/
LiveDocument.prototype._detachFromEditor = function () {
if (this.editor) {
this.hideHighlight();
this.editor.off("cursorActivity", this._onCursorActivity);
}
};
let _disableHighlightOnCursor = false;
/**
* If tur, it will disable highlights in live preview on cursor movement in editor
* @param {boolean} shouldDisable
*/
LiveDocument.prototype.disableHighlightOnCursorActivity = function (shouldDisable) {
// intentionally global. see usage for details
_disableHighlightOnCursor = shouldDisable;
};
/**
* @private
* Handles a cursor change in our attached editor. Updates the highlight in the browser.
* @param {$.Event} event
* @param {Editor} editor
*/
LiveDocument.prototype._onCursorActivity = function (event, editor) {
if (!this.editor) {
return;
}
if(!_disableHighlightOnCursor){
this.updateHighlight();
}
};
/**
* @private
* Update errors shown by the live document in the editor. Should be called by subclasses
* when the list of errors changes.
*/
LiveDocument.prototype._updateErrorDisplay = function () {
var self = this,
startLine,
endLine,
i,
lineHandle;
if (!this.editor) {
return;
}
// Buffer addLineClass DOM changes in a CodeMirror operation
this.editor._codeMirror.operation(function () {
// Remove existing errors before marking new ones
self._clearErrorDisplay();
self._errorLineHandles = self._errorLineHandles || [];
self.errors.forEach(function (error) {
startLine = error.startPos.line;
endLine = error.endPos.line;
for (i = startLine; i < endLine + 1; i++) {
lineHandle = self.editor._codeMirror.addLineClass(i, "wrap", SYNC_ERROR_CLASS);
self._errorLineHandles.push(lineHandle);
}
});
});
this.trigger("errorStatusChanged", !!this.errors.length);
};
/**
* @private
* Clears the errors shown in the attached editor.
*/
LiveDocument.prototype._clearErrorDisplay = function () {
var self = this,
lineHandle;
if (!this.editor ||
!this._errorLineHandles ||
!this._errorLineHandles.length) {
return;
}
this.editor._codeMirror.operation(function () {
while (true) {
// Iterate over all lines that were previously marked with an error
lineHandle = self._errorLineHandles.pop();
if (!lineHandle) {
break;
}
self.editor._codeMirror.removeLineClass(lineHandle, "wrap", SYNC_ERROR_CLASS);
}
});
};
/**
* Returns true if we should be highlighting.
* @return {boolean}
*/
LiveDocument.prototype.isHighlightEnabled = function () {
return PreferencesManager.get(CONSTANTS.PREFERENCE_LIVE_PREVIEW_MODE) !== CONSTANTS.LIVE_PREVIEW_MODE;
};
/**
* Called when the highlight in the browser should be updated because the user has
* changed the selection. Does nothing in base class, should be implemented by subclasses
* that implement highlighting functionality.
*/
LiveDocument.prototype.updateHighlight = function () {
// Does nothing in base class
};
/**
* Hides the current highlight in the browser.
* @param {boolean=} temporary If true, this isn't a change of state - we're just about
* to re-highlight.
*/
LiveDocument.prototype.hideHighlight = function (temporary) {
if (!temporary) {
this._lastHighlight = null;
}
this.protocol.evaluate("_LD.hideHighlight()");
};
/**
* Highlight all nodes affected by a CSS rule. Should be called by subclass implementations of
* `updateHighlight()`.
* @param {string} name The selector whose matched nodes should be highlighted.
*/
LiveDocument.prototype.highlightRule = function (name) {
if (this._lastHighlight === name) {
return;
}
this._lastHighlight = name;
this.protocol.evaluate("_LD.highlightRule(" + JSON.stringify(name) + ")");
};
/**
* Highlight all nodes with 'data-brackets-id' value
* that matches id, or if id is an array, matches any of the given ids.
* Should be called by subclass implementations of
* `updateHighlight()`.
* @param {string|Array.<string>} value of the 'data-brackets-id' to match,
* or an array of such.
*/
LiveDocument.prototype.highlightDomElement = function (ids) {
var selector = "";
if (!Array.isArray(ids)) {
ids = [ids];
}
_.each(ids, function (id) {
if (selector !== "") {
selector += ",";
}
selector += "[data-brackets-id='" + id + "']";
});
this.highlightRule(selector);
};
/**
* Redraw active highlights.
*/
LiveDocument.prototype.redrawHighlights = function () {
if (this.isHighlightEnabled()) {
this.protocol.evaluate("_LD.redrawHighlights()");
}
};
module.exports = LiveDocument;
});