From 090b753d844c4e54a16578ad9be46c063ac86332 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Sun, 24 Sep 2017 10:34:34 -0700 Subject: [PATCH] Move toggleLineCommentsForBufferRows to TokenizedBuffer --- spec/text-editor-spec.js | 156 --------------------------------- spec/tokenized-buffer-spec.js | 158 ++++++++++++++++++++++++++++++++++ src/text-editor.coffee | 70 +-------------- src/tokenized-buffer.js | 94 ++++++++++++++++++++ 4 files changed, 253 insertions(+), 225 deletions(-) diff --git a/spec/text-editor-spec.js b/spec/text-editor-spec.js index 6cbb926de..82ad3bc90 100644 --- a/spec/text-editor-spec.js +++ b/spec/text-editor-spec.js @@ -8,162 +8,6 @@ describe('TextEditor', () => { editor.destroy() }) - describe('.toggleLineCommentsForBufferRows', () => { - describe('xml', () => { - beforeEach(async () => { - editor = await atom.workspace.open('sample.xml', {autoIndent: false}) - editor.setText('') - await atom.packages.activatePackage('language-xml') - }) - - it('removes the leading whitespace from the comment end pattern match when uncommenting lines', () => { - editor.toggleLineCommentsForBufferRows(0, 0) - expect(editor.lineTextForBufferRow(0)).toBe('test') - }) - }) - - describe('less', () => { - beforeEach(async () => { - editor = await atom.workspace.open('sample.less', {autoIndent: false}) - await atom.packages.activatePackage('language-less') - await atom.packages.activatePackage('language-css') - }) - - it('only uses the `commentEnd` pattern if it comes from the same grammar as the `commentStart` when commenting lines', () => { - editor.toggleLineCommentsForBufferRows(0, 0) - expect(editor.lineTextForBufferRow(0)).toBe('// @color: #4D926F;') - }) - }) - - describe('css', () => { - beforeEach(async () => { - editor = await atom.workspace.open('css.css', {autoIndent: false}) - await atom.packages.activatePackage('language-css') - }) - - it('comments/uncomments lines in the given range', () => { - editor.toggleLineCommentsForBufferRows(0, 1) - expect(editor.lineTextForBufferRow(0)).toBe('/*body {') - expect(editor.lineTextForBufferRow(1)).toBe(' font-size: 1234px;*/') - expect(editor.lineTextForBufferRow(2)).toBe(' width: 110%;') - expect(editor.lineTextForBufferRow(3)).toBe(' font-weight: bold !important;') - - editor.toggleLineCommentsForBufferRows(2, 2) - expect(editor.lineTextForBufferRow(0)).toBe('/*body {') - expect(editor.lineTextForBufferRow(1)).toBe(' font-size: 1234px;*/') - expect(editor.lineTextForBufferRow(2)).toBe(' /*width: 110%;*/') - expect(editor.lineTextForBufferRow(3)).toBe(' font-weight: bold !important;') - - editor.toggleLineCommentsForBufferRows(0, 1) - expect(editor.lineTextForBufferRow(0)).toBe('body {') - expect(editor.lineTextForBufferRow(1)).toBe(' font-size: 1234px;') - expect(editor.lineTextForBufferRow(2)).toBe(' /*width: 110%;*/') - expect(editor.lineTextForBufferRow(3)).toBe(' font-weight: bold !important;') - }) - - it('uncomments lines with leading whitespace', () => { - editor.setTextInBufferRange([[2, 0], [2, Infinity]], ' /*width: 110%;*/') - editor.toggleLineCommentsForBufferRows(2, 2) - expect(editor.lineTextForBufferRow(2)).toBe(' width: 110%;') - }) - - it('uncomments lines with trailing whitespace', () => { - editor.setTextInBufferRange([[2, 0], [2, Infinity]], '/*width: 110%;*/ ') - editor.toggleLineCommentsForBufferRows(2, 2) - expect(editor.lineTextForBufferRow(2)).toBe('width: 110%; ') - }) - - it('uncomments lines with leading and trailing whitespace', () => { - editor.setTextInBufferRange([[2, 0], [2, Infinity]], ' /*width: 110%;*/ ') - editor.toggleLineCommentsForBufferRows(2, 2) - expect(editor.lineTextForBufferRow(2)).toBe(' width: 110%; ') - }) - }) - - describe('coffeescript', () => { - beforeEach(async () => { - editor = await atom.workspace.open('coffee.coffee', {autoIndent: false}) - await atom.packages.activatePackage('language-coffee-script') - }) - - it('comments/uncomments lines in the given range', () => { - editor.toggleLineCommentsForBufferRows(4, 6) - expect(editor.lineTextForBufferRow(4)).toBe(' # pivot = items.shift()') - expect(editor.lineTextForBufferRow(5)).toBe(' # left = []') - expect(editor.lineTextForBufferRow(6)).toBe(' # right = []') - - editor.toggleLineCommentsForBufferRows(4, 5) - expect(editor.lineTextForBufferRow(4)).toBe(' pivot = items.shift()') - expect(editor.lineTextForBufferRow(5)).toBe(' left = []') - expect(editor.lineTextForBufferRow(6)).toBe(' # right = []') - }) - - it('comments/uncomments empty lines', () => { - editor.toggleLineCommentsForBufferRows(4, 7) - expect(editor.lineTextForBufferRow(4)).toBe(' # pivot = items.shift()') - expect(editor.lineTextForBufferRow(5)).toBe(' # left = []') - expect(editor.lineTextForBufferRow(6)).toBe(' # right = []') - expect(editor.lineTextForBufferRow(7)).toBe(' # ') - - editor.toggleLineCommentsForBufferRows(4, 5) - expect(editor.lineTextForBufferRow(4)).toBe(' pivot = items.shift()') - expect(editor.lineTextForBufferRow(5)).toBe(' left = []') - expect(editor.lineTextForBufferRow(6)).toBe(' # right = []') - expect(editor.lineTextForBufferRow(7)).toBe(' # ') - }) - }) - - describe('javascript', () => { - beforeEach(async () => { - editor = await atom.workspace.open('sample.js', {autoIndent: false}) - await atom.packages.activatePackage('language-javascript') - }) - - it('comments/uncomments lines in the given range', () => { - editor.toggleLineCommentsForBufferRows(4, 7) - expect(editor.lineTextForBufferRow(4)).toBe(' // while(items.length > 0) {') - expect(editor.lineTextForBufferRow(5)).toBe(' // current = items.shift();') - expect(editor.lineTextForBufferRow(6)).toBe(' // current < pivot ? left.push(current) : right.push(current);') - expect(editor.lineTextForBufferRow(7)).toBe(' // }') - - editor.toggleLineCommentsForBufferRows(4, 5) - expect(editor.lineTextForBufferRow(4)).toBe(' while(items.length > 0) {') - expect(editor.lineTextForBufferRow(5)).toBe(' current = items.shift();') - console.log(JSON.stringify(editor.lineTextForBufferRow(5))); - return - expect(editor.lineTextForBufferRow(6)).toBe(' // current < pivot ? left.push(current) : right.push(current);') - expect(editor.lineTextForBufferRow(7)).toBe(' // }') - - editor.setText('\tvar i;') - editor.toggleLineCommentsForBufferRows(0, 0) - expect(editor.lineTextForBufferRow(0)).toBe('\t// var i;') - - editor.setText('var i;') - editor.toggleLineCommentsForBufferRows(0, 0) - expect(editor.lineTextForBufferRow(0)).toBe('// var i;') - - editor.setText(' var i;') - editor.toggleLineCommentsForBufferRows(0, 0) - expect(editor.lineTextForBufferRow(0)).toBe(' // var i;') - - editor.setText(' ') - editor.toggleLineCommentsForBufferRows(0, 0) - expect(editor.lineTextForBufferRow(0)).toBe(' // ') - - editor.setText(' a\n \n b') - editor.toggleLineCommentsForBufferRows(0, 2) - expect(editor.lineTextForBufferRow(0)).toBe(' // a') - expect(editor.lineTextForBufferRow(1)).toBe(' // ') - expect(editor.lineTextForBufferRow(2)).toBe(' // b') - - editor.setText(' \n // var i;') - editor.toggleLineCommentsForBufferRows(0, 1) - expect(editor.lineTextForBufferRow(0)).toBe(' ') - expect(editor.lineTextForBufferRow(1)).toBe(' var i;') - }) - }) - }) - describe('folding', () => { beforeEach(async () => { await atom.packages.activatePackage('language-javascript') diff --git a/spec/tokenized-buffer-spec.js b/spec/tokenized-buffer-spec.js index f2e435538..ccd605800 100644 --- a/spec/tokenized-buffer-spec.js +++ b/spec/tokenized-buffer-spec.js @@ -639,6 +639,164 @@ describe('TokenizedBuffer', () => { }) }) + describe('.toggleLineCommentsForBufferRows', () => { + let editor + + describe('xml', () => { + beforeEach(async () => { + editor = await atom.workspace.open('sample.xml', {autoIndent: false}) + editor.setText('') + await atom.packages.activatePackage('language-xml') + }) + + it('removes the leading whitespace from the comment end pattern match when uncommenting lines', () => { + editor.toggleLineCommentsForBufferRows(0, 0) + expect(editor.lineTextForBufferRow(0)).toBe('test') + }) + }) + + describe('less', () => { + beforeEach(async () => { + editor = await atom.workspace.open('sample.less', {autoIndent: false}) + await atom.packages.activatePackage('language-less') + await atom.packages.activatePackage('language-css') + }) + + it('only uses the `commentEnd` pattern if it comes from the same grammar as the `commentStart` when commenting lines', () => { + editor.toggleLineCommentsForBufferRows(0, 0) + expect(editor.lineTextForBufferRow(0)).toBe('// @color: #4D926F;') + }) + }) + + describe('css', () => { + beforeEach(async () => { + editor = await atom.workspace.open('css.css', {autoIndent: false}) + await atom.packages.activatePackage('language-css') + }) + + it('comments/uncomments lines in the given range', () => { + editor.toggleLineCommentsForBufferRows(0, 1) + expect(editor.lineTextForBufferRow(0)).toBe('/*body {') + expect(editor.lineTextForBufferRow(1)).toBe(' font-size: 1234px;*/') + expect(editor.lineTextForBufferRow(2)).toBe(' width: 110%;') + expect(editor.lineTextForBufferRow(3)).toBe(' font-weight: bold !important;') + + editor.toggleLineCommentsForBufferRows(2, 2) + expect(editor.lineTextForBufferRow(0)).toBe('/*body {') + expect(editor.lineTextForBufferRow(1)).toBe(' font-size: 1234px;*/') + expect(editor.lineTextForBufferRow(2)).toBe(' /*width: 110%;*/') + expect(editor.lineTextForBufferRow(3)).toBe(' font-weight: bold !important;') + + editor.toggleLineCommentsForBufferRows(0, 1) + expect(editor.lineTextForBufferRow(0)).toBe('body {') + expect(editor.lineTextForBufferRow(1)).toBe(' font-size: 1234px;') + expect(editor.lineTextForBufferRow(2)).toBe(' /*width: 110%;*/') + expect(editor.lineTextForBufferRow(3)).toBe(' font-weight: bold !important;') + }) + + it('uncomments lines with leading whitespace', () => { + editor.setTextInBufferRange([[2, 0], [2, Infinity]], ' /*width: 110%;*/') + editor.toggleLineCommentsForBufferRows(2, 2) + expect(editor.lineTextForBufferRow(2)).toBe(' width: 110%;') + }) + + it('uncomments lines with trailing whitespace', () => { + editor.setTextInBufferRange([[2, 0], [2, Infinity]], '/*width: 110%;*/ ') + editor.toggleLineCommentsForBufferRows(2, 2) + expect(editor.lineTextForBufferRow(2)).toBe('width: 110%; ') + }) + + it('uncomments lines with leading and trailing whitespace', () => { + editor.setTextInBufferRange([[2, 0], [2, Infinity]], ' /*width: 110%;*/ ') + editor.toggleLineCommentsForBufferRows(2, 2) + expect(editor.lineTextForBufferRow(2)).toBe(' width: 110%; ') + }) + }) + + describe('coffeescript', () => { + beforeEach(async () => { + editor = await atom.workspace.open('coffee.coffee', {autoIndent: false}) + await atom.packages.activatePackage('language-coffee-script') + }) + + it('comments/uncomments lines in the given range', () => { + editor.toggleLineCommentsForBufferRows(4, 6) + expect(editor.lineTextForBufferRow(4)).toBe(' # pivot = items.shift()') + expect(editor.lineTextForBufferRow(5)).toBe(' # left = []') + expect(editor.lineTextForBufferRow(6)).toBe(' # right = []') + + editor.toggleLineCommentsForBufferRows(4, 5) + expect(editor.lineTextForBufferRow(4)).toBe(' pivot = items.shift()') + expect(editor.lineTextForBufferRow(5)).toBe(' left = []') + expect(editor.lineTextForBufferRow(6)).toBe(' # right = []') + }) + + it('comments/uncomments empty lines', () => { + editor.toggleLineCommentsForBufferRows(4, 7) + expect(editor.lineTextForBufferRow(4)).toBe(' # pivot = items.shift()') + expect(editor.lineTextForBufferRow(5)).toBe(' # left = []') + expect(editor.lineTextForBufferRow(6)).toBe(' # right = []') + expect(editor.lineTextForBufferRow(7)).toBe(' # ') + + editor.toggleLineCommentsForBufferRows(4, 5) + expect(editor.lineTextForBufferRow(4)).toBe(' pivot = items.shift()') + expect(editor.lineTextForBufferRow(5)).toBe(' left = []') + expect(editor.lineTextForBufferRow(6)).toBe(' # right = []') + expect(editor.lineTextForBufferRow(7)).toBe(' # ') + }) + }) + + describe('javascript', () => { + beforeEach(async () => { + editor = await atom.workspace.open('sample.js', {autoIndent: false}) + await atom.packages.activatePackage('language-javascript') + }) + + it('comments/uncomments lines in the given range', () => { + editor.toggleLineCommentsForBufferRows(4, 7) + expect(editor.lineTextForBufferRow(4)).toBe(' // while(items.length > 0) {') + expect(editor.lineTextForBufferRow(5)).toBe(' // current = items.shift();') + expect(editor.lineTextForBufferRow(6)).toBe(' // current < pivot ? left.push(current) : right.push(current);') + expect(editor.lineTextForBufferRow(7)).toBe(' // }') + + editor.toggleLineCommentsForBufferRows(4, 5) + expect(editor.lineTextForBufferRow(4)).toBe(' while(items.length > 0) {') + expect(editor.lineTextForBufferRow(5)).toBe(' current = items.shift();') + console.log(JSON.stringify(editor.lineTextForBufferRow(5))); + return + expect(editor.lineTextForBufferRow(6)).toBe(' // current < pivot ? left.push(current) : right.push(current);') + expect(editor.lineTextForBufferRow(7)).toBe(' // }') + + editor.setText('\tvar i;') + editor.toggleLineCommentsForBufferRows(0, 0) + expect(editor.lineTextForBufferRow(0)).toBe('\t// var i;') + + editor.setText('var i;') + editor.toggleLineCommentsForBufferRows(0, 0) + expect(editor.lineTextForBufferRow(0)).toBe('// var i;') + + editor.setText(' var i;') + editor.toggleLineCommentsForBufferRows(0, 0) + expect(editor.lineTextForBufferRow(0)).toBe(' // var i;') + + editor.setText(' ') + editor.toggleLineCommentsForBufferRows(0, 0) + expect(editor.lineTextForBufferRow(0)).toBe(' // ') + + editor.setText(' a\n \n b') + editor.toggleLineCommentsForBufferRows(0, 2) + expect(editor.lineTextForBufferRow(0)).toBe(' // a') + expect(editor.lineTextForBufferRow(1)).toBe(' // ') + expect(editor.lineTextForBufferRow(2)).toBe(' // b') + + editor.setText(' \n // var i;') + editor.toggleLineCommentsForBufferRows(0, 1) + expect(editor.lineTextForBufferRow(0)).toBe(' ') + expect(editor.lineTextForBufferRow(1)).toBe(' var i;') + }) + }) + }) + describe('.isFoldableAtRow(row)', () => { beforeEach(() => { buffer = atom.project.bufferForPathSync('sample.js') diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 5ded33bb1..d85e36535 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -3,7 +3,6 @@ path = require 'path' fs = require 'fs-plus' Grim = require 'grim' {CompositeDisposable, Disposable, Emitter} = require 'event-kit' -{OnigRegExp} = require 'oniguruma' {Point, Range} = TextBuffer = require 'text-buffer' DecorationManager = require './decoration-manager' TokenizedBuffer = require './tokenized-buffer' @@ -3887,74 +3886,7 @@ class TextEditor extends Model toggleLineCommentForBufferRow: (row) -> @toggleLineCommentsForBufferRows(row, row) - toggleLineCommentsForBufferRows: (start, end) -> - scope = @scopeDescriptorForBufferPosition([start, 0]) - commentStrings = @getCommentStrings(scope) - return unless commentStrings?.commentStartString - {commentStartString, commentEndString} = commentStrings - - buffer = @buffer - commentStartRegexString = _.escapeRegExp(commentStartString).replace(/(\s+)$/, '(?:$1)?') - commentStartRegex = new OnigRegExp("^(\\s*)(#{commentStartRegexString})") - - if commentEndString - shouldUncomment = commentStartRegex.testSync(buffer.lineForRow(start)) - if shouldUncomment - commentEndRegexString = _.escapeRegExp(commentEndString).replace(/^(\s+)/, '(?:$1)?') - commentEndRegex = new OnigRegExp("(#{commentEndRegexString})(\\s*)$") - startMatch = commentStartRegex.searchSync(buffer.lineForRow(start)) - endMatch = commentEndRegex.searchSync(buffer.lineForRow(end)) - if startMatch and endMatch - buffer.transact -> - columnStart = startMatch[1].length - columnEnd = columnStart + startMatch[2].length - buffer.setTextInRange([[start, columnStart], [start, columnEnd]], "") - - endLength = buffer.lineLengthForRow(end) - endMatch[2].length - endColumn = endLength - endMatch[1].length - buffer.setTextInRange([[end, endColumn], [end, endLength]], "") - else - buffer.transact -> - indentLength = buffer.lineForRow(start).match(/^\s*/)?[0].length ? 0 - buffer.insert([start, indentLength], commentStartString) - buffer.insert([end, buffer.lineLengthForRow(end)], commentEndString) - else - allBlank = true - allBlankOrCommented = true - - for row in [start..end] by 1 - line = buffer.lineForRow(row) - blank = line?.match(/^\s*$/) - - allBlank = false unless blank - allBlankOrCommented = false unless blank or commentStartRegex.testSync(line) - - shouldUncomment = allBlankOrCommented and not allBlank - - if shouldUncomment - for row in [start..end] by 1 - if match = commentStartRegex.searchSync(buffer.lineForRow(row)) - columnStart = match[1].length - columnEnd = columnStart + match[2].length - buffer.setTextInRange([[row, columnStart], [row, columnEnd]], "") - else - indents = [] - for row in [start..end] by 1 - unless @isBufferRowBlank(row) - indents.push(@indentationForBufferRow(start)) - indents.push(0) if indents.length is 0 - indent = Math.min(indents...) - - indentString = @buildIndentString(indent) - tabLength = @getTabLength() - indentRegex = new RegExp("(\t|[ ]{#{tabLength}}){#{Math.floor(indent)}}") - for row in [start..end] by 1 - line = buffer.lineForRow(row) - if indentLength = line.match(indentRegex)?[0].length - buffer.insert([row, indentLength], commentStartString) - else - buffer.setTextInRange([[row, 0], [row, indentString.length]], indentString + commentStartString) - return + toggleLineCommentsForBufferRows: (start, end) -> @tokenizedBuffer.toggleLineCommentsForBufferRows(start, end) rowRangeForParagraphAtBufferRow: (bufferRow) -> return unless NON_WHITESPACE_REGEXP.test(@lineTextForBufferRow(bufferRow)) diff --git a/src/tokenized-buffer.js b/src/tokenized-buffer.js index 546678f57..bdfdc254b 100644 --- a/src/tokenized-buffer.js +++ b/src/tokenized-buffer.js @@ -158,6 +158,94 @@ class TokenizedBuffer { return Math.max(desiredIndentLevel, 0) } + /* + Section - Comments + */ + + toggleLineCommentsForBufferRows (start, end) { + const scope = this.scopeDescriptorForPosition([start, 0]) + const commentStrings = this.commentStringsForScopeDescriptor(scope) + if (!commentStrings) return + const {commentStartString, commentEndString} = commentStrings + if (!commentStartString) return + + const commentStartRegexString = _.escapeRegExp(commentStartString).replace(/(\s+)$/, '(?:$1)?') + const commentStartRegex = new OnigRegExp(`^(\\s*)(${commentStartRegexString})`) + + if (commentEndString) { + const shouldUncomment = commentStartRegex.testSync(this.buffer.lineForRow(start)) + if (shouldUncomment) { + const commentEndRegexString = _.escapeRegExp(commentEndString).replace(/^(\s+)/, '(?:$1)?') + const commentEndRegex = new OnigRegExp(`(${commentEndRegexString})(\\s*)$`) + const startMatch = commentStartRegex.searchSync(this.buffer.lineForRow(start)) + const endMatch = commentEndRegex.searchSync(this.buffer.lineForRow(end)) + if (startMatch && endMatch) { + this.buffer.transact(() => { + const columnStart = startMatch[1].length + const columnEnd = columnStart + startMatch[2].length + this.buffer.setTextInRange([[start, columnStart], [start, columnEnd]], '') + + const endLength = this.buffer.lineLengthForRow(end) - endMatch[2].length + const endColumn = endLength - endMatch[1].length + return this.buffer.setTextInRange([[end, endColumn], [end, endLength]], '') + }) + } + } else { + this.buffer.transact(() => { + const indentLength = this.buffer.lineForRow(start).match(/^\s*/)[0].length + this.buffer.insert([start, indentLength], commentStartString) + this.buffer.insert([end, this.buffer.lineLengthForRow(end)], commentEndString) + }) + } + } else { + let allBlank = true + let allBlankOrCommented = true + + for (let row = start; row <= end; row++) { + const line = this.buffer.lineForRow(row) + const blank = line.match(/^\s*$/) + if (!blank) allBlank = false + if (!blank && !commentStartRegex.testSync(line)) allBlankOrCommented = false + } + + const shouldUncomment = allBlankOrCommented && !allBlank + + if (shouldUncomment) { + for (let row = start; row <= end; row++) { + const match = commentStartRegex.searchSync(this.buffer.lineForRow(row)) + if (match) { + const columnStart = match[1].length + const columnEnd = columnStart + match[2].length + this.buffer.setTextInRange([[row, columnStart], [row, columnEnd]], '') + } + } + } else { + const indents = [] + for (let row = start; row <= end; row++) { + const line = this.buffer.lineForRow(row) + if (NON_WHITESPACE_REGEX.test(line)) { + indents.push(this.indentLevelForLine(line)) + } + } + if (indents.length === 0) indents.push(0) + const indent = Math.min(...indents) + + const tabLength = this.getTabLength() + const indentString = ' '.repeat(tabLength * indent) + const indentRegex = new RegExp(`(\t|[ ]{${tabLength}}){${Math.floor(indent)}}`) + for (let row = start; row <= end; row++) { + const line = this.buffer.lineForRow(row) + const indentMatch = line.match(indentRegex) + if (indentMatch) { + this.buffer.insert([row, indentMatch[0].length], commentStartString) + } else { + this.buffer.insert([row, 0], indentString + commentStartString) + } + } + } + } + } + buildIterator () { return new TokenizedBufferIterator(this) } @@ -720,6 +808,12 @@ class TokenizedBuffer { } } + commentStringsForScopeDescriptor (scopes) { + if (this.scopedSettingsDelegate) { + return this.scopedSettingsDelegate.getCommentStrings(scopes) + } + } + regexForPattern (pattern) { if (pattern) { if (!this.regexesByPattern[pattern]) {