Skip to content
This repository was archived by the owner on Sep 6, 2021. It is now read-only.

Commit 2d9166e

Browse files
committed
Merge pull request #5541 from adobe/nj/update-rule-label
[Initial review] Update rule label when selector is edited
2 parents 36200fd + 82b197a commit 2d9166e

7 files changed

Lines changed: 407 additions & 22 deletions

File tree

src/document/TextRange.js

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ define(function (require, exports, module) {
4040
* Important: you must dispose() a TextRange when you're done with it. Because TextRange addRef()s
4141
* the Document (in order to listen to it), you will leak Documents otherwise.
4242
*
43-
* TextRange dispatches two events:
44-
* - change -- When the range changes (due to a Document change)
43+
* TextRange dispatches these events:
44+
* - change -- When the range boundary line numbers change (due to a Document change)
45+
* - contentChange -- When the actual content of the range changes. This might or might not
46+
* be accompanied by a change in the boundary line numbers.
4547
* - lostSync -- When the backing Document changes in such a way that the range can no longer
4648
* accurately be maintained. Generally, occurs whenever an edit spans a range boundary.
4749
* After this, startLine & endLine will be unusable (set to null).
@@ -86,7 +88,10 @@ define(function (require, exports, module) {
8688

8789
/**
8890
* Applies a single Document change object (out of the linked list of multiple such objects)
89-
* to this range. Returns true if the range was changed as a result.
91+
* to this range.
92+
* @param {Object} change The CodeMirror change record.
93+
* @return {{hasChanged: boolean, hasContentChanged: boolean}} Whether the range boundary
94+
* and/or content has changed.
9095
*/
9196
TextRange.prototype._applySingleChangeToRange = function (change) {
9297
// console.log(this + " applying change to (" +
@@ -97,7 +102,7 @@ define(function (require, exports, module) {
97102
if (!change.from || !change.to) {
98103
this.startLine = null;
99104
this.endLine = null;
100-
return true;
105+
return {hasChanged: true, hasContentChanged: true};
101106

102107
// Special case: certain changes around the edges of the range are problematic, because
103108
// if they're undone, we'll be unable to determine how to fix up the range to include the
@@ -116,29 +121,36 @@ define(function (require, exports, module) {
116121
(change.from.line <= this.endLine && change.to.line > this.endLine)) {
117122
this.startLine = null;
118123
this.endLine = null;
119-
return true;
124+
return {hasChanged: true, hasContentChanged: true};
120125

121126
// Normal case: update the range end points if any content was added before them. Note that
122127
// we don't rely on line handles for this since we want to gracefully handle cases where the
123128
// start or end line was deleted during a change.
124129
} else {
125130
var numAdded = change.text.length - (change.to.line - change.from.line + 1);
126-
var hasChanged = false;
131+
var result = {hasChanged: false, hasContentChanged: false};
127132

128133
// This logic is so simple because we've already excluded all cases where the change
129134
// crosses the range boundaries
130-
if (change.to.line < this.startLine) {
131-
this.startLine += numAdded;
132-
hasChanged = true;
135+
if (numAdded !== 0) {
136+
if (change.to.line < this.startLine) {
137+
this.startLine += numAdded;
138+
result.hasChanged = true;
139+
}
140+
if (change.to.line <= this.endLine) {
141+
this.endLine += numAdded;
142+
result.hasChanged = true;
143+
}
133144
}
134-
if (change.to.line <= this.endLine) {
135-
this.endLine += numAdded;
136-
hasChanged = true;
145+
if (change.from.line >= this.startLine && change.from.line <= this.endLine) {
146+
// Since we know the change doesn't cross the range boundary, as long as the
147+
// start of the change is within the range, we know the content changed.
148+
result.hasContentChanged = true;
137149
}
138150

139151
// console.log("Now " + this);
140152

141-
return hasChanged;
153+
return result;
142154
}
143155
};
144156

@@ -148,12 +160,13 @@ define(function (require, exports, module) {
148160
* range can no longer be accurately maintained.
149161
*/
150162
TextRange.prototype._applyChangesToRange = function (changeList) {
151-
var hasChanged = false;
163+
var hasChanged = false, hasContentChanged = false;
152164
var change;
153165
for (change = changeList; change; change = change.next) {
154166
// Apply this step of the change list
155167
var result = this._applySingleChangeToRange(change);
156-
hasChanged = hasChanged || result;
168+
hasChanged = hasChanged || result.hasChanged;
169+
hasContentChanged = hasContentChanged || result.hasContentChanged;
157170

158171
// If we lost sync with the range, just bail now
159172
if (this.startLine === null || this.endLine === null) {
@@ -165,6 +178,9 @@ define(function (require, exports, module) {
165178
if (hasChanged) {
166179
$(this).triggerHandler("change");
167180
}
181+
if (hasContentChanged) {
182+
$(this).triggerHandler("contentChange");
183+
}
168184
};
169185

170186
TextRange.prototype._handleDocumentChange = function (event, doc, changeList) {

src/editor/CSSInlineEditor.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,10 +229,10 @@ define(function (require, exports, module) {
229229
});
230230
return result;
231231
}
232-
232+
233233
CSSUtils.findMatchingRules(selectorName, hostEditor.document)
234234
.done(function (rules) {
235-
cssInlineEditor = new MultiRangeInlineEditor(rules || [], _getNoRulesMsg);
235+
cssInlineEditor = new MultiRangeInlineEditor(CSSUtils.consolidateRules(rules) || [], _getNoRulesMsg, CSSUtils.getRangeSelectors);
236236
cssInlineEditor.load(hostEditor);
237237

238238
var $header = $(".inline-editor-header", cssInlineEditor.$htmlContent);

src/editor/MultiRangeInlineEditor.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,10 @@ define(function (require, exports, module) {
7474
SearchResultItem.prototype.textRange = null;
7575
SearchResultItem.prototype.$listItem = null;
7676

77-
function _updateRangeLabel(listItem, range) {
77+
function _updateRangeLabel(listItem, range, labelCB) {
78+
if (labelCB) {
79+
range.name = labelCB(range.textRange);
80+
}
7881
var text = StringUtils.htmlEscape(range.name) + " <span class='related-file'>— " + StringUtils.htmlEscape(range.textRange.document.file.name) + " : " + (range.textRange.startLine + 1) + "</span>";
7982
listItem.html(text);
8083
listItem.attr("title", listItem.text());
@@ -85,9 +88,11 @@ define(function (require, exports, module) {
8588
* @param {Array.<{name:String,document:Document,lineStart:number,lineEnd:number}>} ranges The text ranges to display.
8689
* @param {function(): $.Promise} messageCB An optional callback that returns a promise that will be resolved with a message to show
8790
* when no matches are available.
91+
* @param {function(range): string} labelCB An optional callback that returns an updated label string for the given range. Called
92+
* when we detect that the content of one of the ranges has changed.
8893
* @extends {InlineTextEditor}
8994
*/
90-
function MultiRangeInlineEditor(ranges, messageCB) {
95+
function MultiRangeInlineEditor(ranges, messageCB, labelCB) {
9196
InlineTextEditor.call(this);
9297

9398
// Store the results to show in the range list. This creates TextRanges bound to the Document,
@@ -96,6 +101,7 @@ define(function (require, exports, module) {
96101
return new SearchResultItem(rangeResult);
97102
});
98103
this._messageCB = messageCB;
104+
this._labelCB = labelCB;
99105

100106
this._selectedRangeIndex = -1;
101107
}
@@ -113,6 +119,7 @@ define(function (require, exports, module) {
113119
MultiRangeInlineEditor.prototype._ranges = null;
114120
MultiRangeInlineEditor.prototype._selectedRangeIndex = null;
115121
MultiRangeInlineEditor.prototype._messageCB = null;
122+
MultiRangeInlineEditor.prototype._labelCB = null;
116123

117124
/**
118125
* @private
@@ -133,6 +140,8 @@ define(function (require, exports, module) {
133140
// Update list item as TextRange changes
134141
$(range.textRange).on("change", function () {
135142
_updateRangeLabel($rangeItem, range);
143+
}).on("contentChange", function () {
144+
_updateRangeLabel($rangeItem, range, self._labelCB);
136145
});
137146

138147
// If TextRange lost sync, remove it from the list (and close the widget if no other ranges are left)

src/language/CSSUtils.js

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,8 @@ define(function (require, exports, module) {
504504
starts that this selector (e.g. .baz) is part of. Particularly relevant for
505505
groups that are on multiple lines.
506506
selectorGroupStartChar: column in line where the selector group starts.
507+
selectorGroup: the entire selector group containing this selector, or undefined if there
508+
is only one selector in the rule.
507509
declListStartLine: line where the declaration list for the rule starts
508510
declListStartChar: column in line where the declaration list for the rule starts
509511
declListEndLine: line where the declaration list for the rule ends
@@ -690,6 +692,25 @@ define(function (require, exports, module) {
690692
var j;
691693
declListStartLine = line;
692694
declListStartChar = stream.start;
695+
696+
// Extract the entire selector group we just saw.
697+
var selectorGroup, sgLine;
698+
if (selectorGroupStartLine !== -1) {
699+
selectorGroup = "";
700+
for (sgLine = selectorGroupStartLine; sgLine <= declListStartLine; sgLine++) {
701+
var startChar = 0, endChar = lines[sgLine].length;
702+
if (sgLine === selectorGroupStartLine) {
703+
startChar = selectorGroupStartChar;
704+
} else {
705+
selectorGroup += " "; // replace the newline with a single space
706+
}
707+
if (sgLine === declListStartLine) {
708+
endChar = declListStartChar;
709+
}
710+
selectorGroup += lines[sgLine].substring(startChar, endChar);
711+
}
712+
selectorGroup = selectorGroup.trim();
713+
}
693714

694715
// Since we're now in a declaration list, that means we also finished
695716
// parsing the whole selector group. Therefore, reset selectorGroupStartLine
@@ -706,7 +727,7 @@ define(function (require, exports, module) {
706727
}
707728
}
708729

709-
// assign this declaration list position to every selector on the stack
730+
// assign this declaration list position and selector group to every selector on the stack
710731
// that doesn't have a declaration list start and end line
711732
for (j = selectors.length - 1; j >= 0; j--) {
712733
if (selectors[j].declListEndLine !== -1) {
@@ -716,6 +737,9 @@ define(function (require, exports, module) {
716737
selectors[j].declListStartChar = declListStartChar;
717738
selectors[j].declListEndLine = line;
718739
selectors[j].declListEndChar = stream.pos - 1; // stream.pos actually points to the char after the }
740+
if (selectorGroup) {
741+
selectors[j].selectorGroup = selectorGroup;
742+
}
719743
}
720744
}
721745
}
@@ -915,7 +939,8 @@ define(function (require, exports, module) {
915939
name: selectorInfo.selector,
916940
document: sourceDoc,
917941
lineStart: selectorInfo.ruleStartLine + lineOffset,
918-
lineEnd: selectorInfo.declListEndLine + lineOffset
942+
lineEnd: selectorInfo.declListEndLine + lineOffset,
943+
selectorGroup: selectorInfo.selectorGroup
919944
});
920945
});
921946
}
@@ -1220,13 +1245,66 @@ define(function (require, exports, module) {
12201245
};
12211246
}
12221247

1248+
/**
1249+
*
1250+
* In the given rule array (as returned by `findMatchingRules()`), if multiple rules in a row
1251+
* refer to the same rule (because there were multiple matching selectors), eliminate the redundant
1252+
* rules and use the selectorGroup as the name instead of the original selector.
1253+
*/
1254+
function consolidateRules(rules) {
1255+
var newRules = [], lastRule;
1256+
rules.forEach(function (rule) {
1257+
if (lastRule &&
1258+
rule.document === lastRule.document &&
1259+
rule.lineStart === lastRule.lineStart &&
1260+
rule.lineEnd === lastRule.lineEnd &&
1261+
rule.selectorGroup === lastRule.selectorGroup) {
1262+
lastRule.name = lastRule.selectorGroup;
1263+
} else {
1264+
newRules.push(rule);
1265+
}
1266+
lastRule = rule;
1267+
});
1268+
return newRules;
1269+
}
1270+
1271+
/**
1272+
* Given a TextRange, extracts the selector(s) for the rule in the range and returns it.
1273+
* Assumes the range only contains one rule; if there's more than one, it will return the
1274+
* selector(s) for the first rule.
1275+
* @param {TextRange} range The range to extract the selector(s) from.
1276+
* @return {string} The selector(s) for the rule in the range.
1277+
*/
1278+
function getRangeSelectors(range) {
1279+
// There's currently no immediate way to access a given line in a Document, because it's just
1280+
// stored as a string. Eventually, we should have Documents cache the lines in the document
1281+
// as well, or make them use CodeMirror documents which do the same thing.
1282+
var i, startIndex = 0, endIndex, text = range.document.getText();
1283+
for (i = 0; i < range.startLine; i++) {
1284+
startIndex = text.indexOf("\n", startIndex) + 1;
1285+
}
1286+
endIndex = startIndex;
1287+
// Go one line past the end line. We'll extract text up to but not including the last newline.
1288+
for (i = range.startLine + 1; i <= range.endLine + 1; i++) {
1289+
endIndex = text.indexOf("\n", endIndex) + 1;
1290+
}
1291+
var allSelectors = extractAllSelectors(text.substring(startIndex, endIndex));
1292+
1293+
// There should only be one rule in the range, and if there are multiple selectors for
1294+
// the first rule, they'll all be recorded in the "selectorGroup" for the first selector,
1295+
// so we only need to look at the first one.
1296+
return (allSelectors.length ? allSelectors[0].selectorGroup || allSelectors[0].selector : "");
1297+
}
1298+
12231299
exports._findAllMatchingSelectorsInText = _findAllMatchingSelectorsInText; // For testing only
12241300
exports.findMatchingRules = findMatchingRules;
12251301
exports.extractAllSelectors = extractAllSelectors;
12261302
exports.extractAllNamedFlows = extractAllNamedFlows;
12271303
exports.findSelectorAtDocumentPos = findSelectorAtDocumentPos;
12281304
exports.reduceStyleSheetForRegExParsing = reduceStyleSheetForRegExParsing;
12291305
exports.addRuleToDocument = addRuleToDocument;
1306+
exports.consolidateRules = consolidateRules;
1307+
exports.getRangeSelectors = getRangeSelectors;
12301308

12311309
exports.SELECTOR = SELECTOR;
12321310
exports.PROP_NAME = PROP_NAME;

test/UnitTestSuite.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ define(function (require, exports, module) {
6464
require("spec/QuickOpen-test");
6565
require("spec/RemoteFunctions-test");
6666
require("spec/StringMatch-test");
67+
require("spec/TextRange-test");
6768
require("spec/UpdateNotification-test");
6869
require("spec/ViewCommandHandlers-test");
6970
require("spec/ViewUtils-test");

0 commit comments

Comments
 (0)