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

Commit 4452285

Browse files
committed
First phase of token iteration cleanup: create a TokenIterator class and
migrate existing clients of TokenUtils. Simplifies code that skips whitespace but doesn't change much else yet, beyond a few specific cleanups: - add & clarify docs in EditorCommandHandlers - in blockCommentPrefixSuffix(), make start/endStream effectively immutable per TomM in #3060 - in CSSUtils, simplify how streams are cloned & remove unneeded pos cloning - in HTMLUtils.getTagInfo() remove unneeded 2nd iterator
1 parent 0a2d421 commit 4452285

File tree

5 files changed

+489
-281
lines changed

5 files changed

+489
-281
lines changed

src/editor/EditorCommandHandlers.js

Lines changed: 76 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ define(function (require, exports, module) {
3737
CommandManager = require("command/CommandManager"),
3838
EditorManager = require("editor/EditorManager"),
3939
StringUtils = require("utils/StringUtils"),
40+
TokenStream = require("language/TokenStream"),
4041
TokenUtils = require("utils/TokenUtils");
4142

4243
/**
@@ -193,57 +194,60 @@ define(function (require, exports, module) {
193194

194195
/**
195196
* @private
196-
* Moves the token context to the token that starts the block-comment. Ctx starts in a block-comment.
197+
* Moves the stream to the token that starts the block-comment.
197198
* Returns the position of the prefix or null if gets to the start of the document and didn't found it.
198-
* @param {!{editor:{CodeMirror}, pos:{ch:{string}, line:{number}}, token:{object}}} ctx - token context
199+
* @param {!TokenStream} stream - positioned somewhere within the block comment (may be ON the start or end token)
199200
* @param {!RegExp} prefixExp - a valid regular expression
200201
* @return {?{line: number, ch: number}}
201202
*/
202-
function _findCommentStart(ctx, prefixExp) {
203+
function _findCommentStart(stream, prefixExp) {
203204
var result = true;
204205

205-
while (result && !ctx.token.string.match(prefixExp)) {
206-
result = TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, ctx);
206+
while (result && !stream.token.string.match(prefixExp)) {
207+
result = stream.prevSkipWs();
207208
}
208-
return result ? {line: ctx.pos.line, ch: ctx.token.start} : null;
209+
return result ? {line: stream.pos.line, ch: stream.token.start} : null;
209210
}
210211

211212
/**
212213
* @private
213-
* Moves the token context to the token that ends the block-comment. Ctx starts in a block-comment.
214+
* Moves the stream to the token that ends the block-comment.
214215
* Returns the position of the sufix or null if gets to the end of the document and didn't found it.
215-
* @param {!{editor:{CodeMirror}, pos:{ch:{string}, line:{number}}, token:{object}}} ctx - token context
216+
* @param {!TokenStream} stream - positioned somewhere within the block comment (may be ON the start or end token)
216217
* @param {!RegExp} suffixExp - a valid regular expression
217218
* @param {!number} suffixLen - length of the suffix
218219
* @return {?{line: number, ch: number}}
219220
*/
220-
function _findCommentEnd(ctx, suffixExp, suffixLen) {
221+
function _findCommentEnd(stream, suffixExp, suffixLen) {
221222
var result = true;
222223

223-
while (result && !ctx.token.string.match(suffixExp)) {
224-
result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx);
224+
while (result && !stream.token.string.match(suffixExp)) {
225+
result = stream.nextSkipWs();
225226
}
226-
return result ? {line: ctx.pos.line, ch: ctx.token.end - suffixLen} : null;
227+
return result ? {line: stream.pos.line, ch: stream.token.end - suffixLen} : null;
227228
}
228229

229230
/**
230231
* @private
231-
* Moves the token context to the next block-comment if there is one before end.
232-
* @param {!{editor:{CodeMirror}, pos:{ch:{string}, line:{number}}, token:{object}}} ctx - token context
232+
* Finds any block comment starting somewhere between the initial stream pos and the given end pos (inclusive).
233+
* The block comment may end after the 'end' pos. Returns true if such a comment was found and leaves stream
234+
* on the block comment open token. Else returns false and leaves stream on the token after 'end'.
235+
* @param {!TokenStream} stream
233236
* @param {!{line: number, ch: number}} end - where to stop searching
234237
* @param {!RegExp} prefixExp - a valid regular expression
235238
* @return {boolean} - true if it found a block-comment
236239
*/
237-
function _findNextBlockComment(ctx, end, prefixExp) {
238-
var index = ctx.editor.indexFromPos(end),
239-
inside = ctx.editor.indexFromPos(ctx.pos) <= index,
240+
function _findNextBlockComment(stream, end, prefixExp) {
241+
// TODO: don't use TokenStream._editor
242+
var index = stream._editor._codeMirror.indexFromPos(end),
243+
inside = stream._editor._codeMirror.indexFromPos(stream.pos) <= index,
240244
result = true;
241245

242-
while (result && inside && !ctx.token.string.match(prefixExp)) {
243-
result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx);
244-
inside = ctx.editor.indexFromPos(ctx.pos) <= index;
246+
while (result && inside && !stream.token.string.match(prefixExp)) {
247+
result = stream.nextSkipWs();
248+
inside = stream._editor._codeMirror.indexFromPos(stream.pos) <= index;
245249
}
246-
return result && inside && !!ctx.token.string.match(prefixExp);
250+
return result && inside && !!stream.token.string.match(prefixExp);
247251
}
248252

249253
/**
@@ -268,39 +272,38 @@ define(function (require, exports, module) {
268272

269273
var doc = editor.document,
270274
sel = editor.getSelection(),
271-
ctx = TokenUtils.getInitialContext(editor._codeMirror, {line: sel.start.line, ch: sel.start.ch}),
272-
startCtx = TokenUtils.getInitialContext(editor._codeMirror, {line: sel.start.line, ch: sel.start.ch}),
273-
endCtx = TokenUtils.getInitialContext(editor._codeMirror, {line: sel.end.line, ch: sel.end.ch}),
275+
stream = TokenStream.forEditor(editor, sel.start),
276+
startStream = TokenStream.forEditor(editor, sel.start), // TODO: never moved; we could just store the token
277+
endStream = TokenStream.forEditor(editor, sel.end), // TODO: never moved; we could just store the token
274278
prefixExp = new RegExp("^" + StringUtils.regexEscape(prefix), "g"),
275279
suffixExp = new RegExp(StringUtils.regexEscape(suffix) + "$", "g"),
276280
lineExp = _createLineExpressions(linePrefixes),
277281
prefixPos = null,
278282
suffixPos = null,
279-
canComment = false,
280-
invalidComment = false,
281-
lineUncomment = false,
283+
canComment = false, // if true, block-comment; if false, block-uncomment using prefix/suffixPos
284+
invalidComment = false, // if true, we never make any changes
285+
lineUncomment = false, // if true, ignore canComment and always line-uncomment
282286
newSelection;
283287

284288
var result, text, line;
285289

286290
// Move the context to the first non-empty token.
287-
if (!ctx.token.className && ctx.token.string.trim().length === 0) {
288-
result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx);
291+
if (!stream.token.className && stream.token.string.trim().length === 0) {
292+
result = stream.nextSkipWs();
289293
}
290294

291-
// Check if we should just do a line uncomment (if all lines in the selection are commented).
292-
if (lineExp.length && (_matchExpressions(ctx.token.string, lineExp) || _matchExpressions(endCtx.token.string, lineExp))) {
293-
var startCtxIndex = editor.indexFromPos({line: ctx.pos.line, ch: ctx.token.start});
294-
var endCtxIndex = editor.indexFromPos({line: endCtx.pos.line, ch: endCtx.token.start + endCtx.token.string.length});
295-
296-
// Find if we aren't actually inside a block-comment
295+
// Check if we should just do a line uncomment (if selected lines are nothing but line comments and optionally whitespace).
296+
// Note: a line comment is always a single token including the "//" (i.e. all line comment tokens match lineExp)
297+
if (lineExp.length && (_matchExpressions(stream.token.string, lineExp) || _matchExpressions(endStream.token.string, lineExp))) {
298+
// Find if line comment(s) are nested inside a block comment - find the first non-line-comment, non-whitespace
299+
// token before our range
297300
result = true;
298-
while (result && _matchExpressions(ctx.token.string, lineExp)) {
299-
result = TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, ctx);
301+
while (result && _matchExpressions(stream.token.string, lineExp)) {
302+
result = stream.prevSkipWs();
300303
}
301304

302305
// If we aren't in a block-comment.
303-
if (!result || ctx.token.className !== "comment" || ctx.token.string.match(suffixExp)) {
306+
if (!result || stream.token.className !== "comment" || stream.token.string.match(suffixExp)) {
304307
// Is a range of text selected? (vs just an insertion pt)
305308
var hasSelection = (sel.start.line !== sel.end.line) || (sel.start.ch !== sel.end.ch);
306309

@@ -310,7 +313,8 @@ define(function (require, exports, module) {
310313
endLine--;
311314
}
312315

313-
// Find if all the lines are line-commented.
316+
// Find if there's any UN-commented text in the selection (vs. nothing but whitespace & line comments)
317+
// If not, then we'll just line-uncomment
314318
if (!_containsUncommented(editor, sel.start.line, endLine, lineExp)) {
315319
lineUncomment = true;
316320

@@ -319,57 +323,63 @@ define(function (require, exports, module) {
319323
canComment = true;
320324
}
321325
} else {
322-
prefixPos = _findCommentStart(startCtx, prefixExp);
323-
suffixPos = _findCommentEnd(startCtx, suffixExp, suffix.length);
326+
// We are inside a block comment, so find its bounds for uncommenting
327+
prefixPos = _findCommentStart(stream, prefixExp);
328+
suffixPos = _findCommentEnd(stream, suffixExp, suffix.length);
324329
}
325330

326-
// If we are in a selection starting and ending in invalid tokens and with no content (not considering spaces),
327-
// find if we are inside a block-comment.
328-
} else if (startCtx.token.className === null && endCtx.token.className === null &&
329-
!editor.posWithinRange(ctx.pos, startCtx.pos, endCtx.pos)) {
330-
result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, startCtx);
331+
// If selection encompasses only whitespace (because 'stream' is already outside range after a single nextSkipWs())
332+
// Whitespace tokens are ambiguous: might be inside a block comment (next case below), or might be in uncommented
333+
// code ("nothing was found" part of case after next below). Look ahead of start token to see which.
334+
} else if (startStream.token.className === null && endStream.token.className === null &&
335+
!editor.posWithinRange(stream.pos, startStream.pos, endStream.pos)) {
331336

332337
// We found a comment, find the start and end and check if the selection is inside the block-comment.
333-
if (startCtx.token.className === "comment") {
334-
prefixPos = _findCommentStart(startCtx, prefixExp);
335-
suffixPos = _findCommentEnd(startCtx, suffixExp, suffix.length);
338+
if (stream.token.className === "comment") {
339+
// We found a comment token after the selection, but does block start before the selection or after it?
340+
// (it can't start IN selection since selection it's all whitespace)
341+
prefixPos = _findCommentStart(stream, prefixExp);
342+
suffixPos = _findCommentEnd(stream, suffixExp, suffix.length);
336343

344+
// Uncomment if selection lies inside the comment (else we'll create new block comment)
337345
if (prefixPos !== null && suffix !== null && !editor.posWithinRange(sel.start, prefixPos, suffixPos)) {
338346
canComment = true;
339347
}
340348
} else {
349+
// Whitespace is surrounded by plain code - so we'll create a new block comment
341350
canComment = true;
342351
}
343352

344-
// If the start is inside a comment, find the prefix and suffix positions.
345-
} else if (ctx.token.className === "comment") {
346-
prefixPos = _findCommentStart(ctx, prefixExp);
347-
suffixPos = _findCommentEnd(ctx, suffixExp, suffix.length);
353+
// If the start is inside a comment, find its bounds for uncommenting
354+
} else if (stream.token.className === "comment") {
355+
prefixPos = _findCommentStart(stream, prefixExp);
356+
suffixPos = _findCommentEnd(stream, suffixExp, suffix.length);
348357

349358
// If not try to find the first comment inside the selection.
350359
} else {
351-
result = _findNextBlockComment(ctx, sel.end, prefixExp);
360+
result = _findNextBlockComment(stream, sel.end, prefixExp);
352361

353-
// If nothing was found is ok to comment.
362+
// If nothing was found then we're creating a new block comment
354363
if (!result) {
355364
canComment = true;
356365
} else {
357-
if (!ctx.token.string.match(prefixExp)) {
358-
prefixPos = _findCommentStart(ctx, prefixExp);
366+
// Found a block comment to uncomment
367+
if (!stream.token.string.match(prefixExp)) {
368+
prefixPos = _findCommentStart(stream, prefixExp);
359369
} else {
360-
prefixPos = {line: ctx.pos.line, ch: ctx.token.start};
370+
prefixPos = {line: stream.pos.line, ch: stream.token.start};
361371
}
362-
suffixPos = _findCommentEnd(ctx, suffixExp, suffix.length);
372+
suffixPos = _findCommentEnd(stream, suffixExp, suffix.length);
363373
}
364374
}
365375

366-
// Search if there is another comment in the selection. Do nothing if there is one.
376+
// Search if there is > 1 block comment in the selection. Do nothing if so.
367377
if (!canComment && !invalidComment && !lineUncomment && suffixPos) {
368378
var start = {line: suffixPos.line, ch: suffixPos.ch + suffix.length + 1};
369379
if (editor.posWithinRange(start, sel.start, sel.end)) {
370380
// Start searching at the next token, if there is one.
371-
result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx) &&
372-
_findNextBlockComment(ctx, sel.end, prefixExp);
381+
result = stream.nextSkipWs() &&
382+
_findNextBlockComment(stream, sel.end, prefixExp);
373383

374384
if (result) {
375385
invalidComment = true;
@@ -487,10 +497,10 @@ define(function (require, exports, module) {
487497
}
488498

489499
// If the selection includes a comment or is already a line selection, delegate to Block-Comment
490-
var ctx = TokenUtils.getInitialContext(editor._codeMirror, {line: selStart.line, ch: selStart.ch});
491-
var result = TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx);
492-
var className = ctx.token.className;
493-
result = result && _findNextBlockComment(ctx, selEnd, prefixExp);
500+
var stream = TokenStream.forEditor(editor, selStart);
501+
var result = stream.nextSkipWs();
502+
var className = stream.token.className;
503+
result = result && _findNextBlockComment(stream, selEnd, prefixExp);
494504

495505
if (className === "comment" || result || isLineSelection) {
496506
blockCommentPrefixSuffix(editor, prefix, suffix, []);

src/extensions/default/CSSCodeHints/main.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ define(function (require, exports, module) {
77
var AppInit = brackets.getModule("utils/AppInit"),
88
CodeHintManager = brackets.getModule("editor/CodeHintManager"),
99
CSSUtils = brackets.getModule("language/CSSUtils"),
10-
TokenUtils = brackets.getModule("utils/TokenUtils"),
10+
TokenStream = brackets.getModule("language/TokenStream"),
1111
CSSProperties = require("text!CSSProperties.json"),
1212
properties = JSON.parse(CSSProperties);
1313

@@ -144,7 +144,7 @@ define(function (require, exports, module) {
144144
keepHints = false,
145145
adjustCursor = false,
146146
newCursor,
147-
ctx;
147+
stream;
148148

149149
if (this.info.context !== CSSUtils.PROP_NAME && this.info.context !== CSSUtils.PROP_VALUE) {
150150
return false;
@@ -166,13 +166,13 @@ define(function (require, exports, module) {
166166
// the current property name. If a colon already exists, then we also
167167
// adjust the cursor position and show code hints for property values.
168168
end.ch = start.ch + this.info.name.length;
169-
ctx = TokenUtils.getInitialContext(this.editor._codeMirror, cursor);
170-
if (ctx.token.string.length > 0 && !ctx.token.string.match(/\S/)) {
169+
stream = TokenStream.forEditor(this.editor, cursor);
170+
if (stream.token.string.length > 0 && !stream.token.string.match(/\S/)) {
171171
// We're at the very beginning of a property name. So skip it
172172
// before we locate the colon following it.
173-
TokenUtils.moveNextToken(ctx);
173+
stream.next();
174174
}
175-
if (TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx) && ctx.token.string === ":") {
175+
if (stream.nextSkipWs() && stream.token.string === ":") {
176176
adjustCursor = true;
177177
newCursor = { line: cursor.line,
178178
ch: cursor.ch + (hint.length - this.info.name.length) };

0 commit comments

Comments
 (0)