From 88e246b622283e72f4f679be561bd22a1802a81f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 29 Oct 2012 13:05:42 -0600 Subject: [PATCH 1/4] Hide the cursor when the selection is not empty --- spec/app/editor-spec.coffee | 13 +++++++++++++ src/app/cursor-view.coffee | 17 +++++++++++------ src/app/cursor.coffee | 8 ++++++++ src/app/selection.coffee | 14 ++++++++------ 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index 7caf1fb69..cc1cfe814 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -864,6 +864,19 @@ describe "Editor", -> advanceClock(100) expect(cursorView).toHaveClass 'idle' + it "hides the cursor when the selection is non-empty, and shows it otherwise", -> + cursorView = editor.getCursorView() + expect(editor.getSelection().isEmpty()).toBeTruthy() + expect(cursorView).toBeVisible() + + editor.setSelectedBufferRange([[0, 0], [3, 0]]) + expect(editor.getSelection().isEmpty()).toBeFalsy() + expect(cursorView).not.toBeVisible() + + editor.setCursorBufferPosition([1, 3]) + expect(editor.getSelection().isEmpty()).toBeTruthy() + expect(cursorView).toBeVisible() + describe "auto-scrolling", -> it "only auto-scrolls when the last cursor is moved", -> editor.setCursorBufferPosition([11,0]) diff --git a/src/app/cursor-view.coffee b/src/app/cursor-view.coffee index 69708acc9..75161ff8e 100644 --- a/src/app/cursor-view.coffee +++ b/src/app/cursor-view.coffee @@ -10,7 +10,7 @@ class CursorView extends View @pre class: 'cursor idle', => @raw ' ' editor: null - hidden: false + visible: true initialize: (@cursor, @editor) -> @cursor.on 'change-screen-position.cursor-view', (screenPosition, { bufferChange }) => @@ -18,6 +18,7 @@ class CursorView extends View @removeIdleClassTemporarily() unless bufferChange @trigger 'cursor-move', {bufferChange} + @cursor.on 'change-visibility.cursor-view', (visible) => @setVisible(visible) @cursor.on 'destroy.cursor-view', => @remove() afterAttach: (onDom) -> @@ -38,12 +39,16 @@ class CursorView extends View if @cursor == @editor.getLastCursor() @editor.scrollTo(pixelPosition) - if @editor.isFoldedAtScreenRow(screenPosition.row) - @hide() unless @hidden - @hidden = true + @setVisible(@cursor.isVisible() and not @editor.isFoldedAtScreenRow(screenPosition.row)) + + setVisible: (visible) -> + return if visible == @visible + @visible = visible + + if @visible + @show() else - @show() if @hidden - @hidden = false + @hide() getBufferPosition: -> @cursor.getBufferPosition() diff --git a/src/app/cursor.coffee b/src/app/cursor.coffee index 4a5a487ea..4f4c91312 100644 --- a/src/app/cursor.coffee +++ b/src/app/cursor.coffee @@ -10,6 +10,7 @@ class Cursor bufferPosition: null goalColumn: null wordRegex: /(\w+)|([^\w\s]+)/g + visible: true constructor: ({@editSession, screenPosition, bufferPosition}) -> @anchor = @editSession.addAnchor(strong: true) @@ -38,6 +39,13 @@ class Cursor getBufferPosition: -> @anchor.getBufferPosition() + setVisible: (visible) -> + if @visible != visible + @visible = visible + @trigger 'change-visibility', @visible + + isVisible: -> @visible + clearSelection: -> if @selection @selection.clear() unless @selection.retainSelection diff --git a/src/app/selection.coffee b/src/app/selection.coffee index 50b6ba84e..3121fe678 100644 --- a/src/app/selection.coffee +++ b/src/app/selection.coffee @@ -11,7 +11,7 @@ class Selection @cursor.selection = this @cursor.on 'change-screen-position.selection', (e) => - @emitChangeScreenRangeEvent() unless e.bufferChanged + @screenRangeChanged() unless e.bufferChanged @cursor.on 'destroy.selection', => @cursor = null @@ -67,13 +67,18 @@ class Selection else new Range(@cursor.getBufferPosition(), @cursor.getBufferPosition()) + screenRangeChanged: -> + screenRange = @getScreenRange() + @trigger 'change-screen-range', screenRange + @cursor?.setVisible(screenRange.isEmpty()) + getText: -> @editSession.buffer.getTextInRange(@getBufferRange()) clear: -> @anchor?.destroy() @anchor = null - @emitChangeScreenRangeEvent() + @screenRangeChanged() selectWord: -> @setBufferRange(@cursor.getCurrentWordBufferRange()) @@ -310,10 +315,7 @@ class Selection placeAnchor: -> @anchor = @editSession.addAnchor(strong: true) @anchor.setScreenPosition(@cursor.getScreenPosition()) - @anchor.on 'change-screen-position.selection', => @emitChangeScreenRangeEvent() - - emitChangeScreenRangeEvent: -> - @trigger 'change-screen-range', @getScreenRange() + @anchor.on 'change-screen-position.selection', => @screenRangeChanged() intersectsBufferRange: (bufferRange) -> @getBufferRange().intersectsWith(bufferRange) From db3d788664079dc446561201f3af9755aeba482a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 29 Oct 2012 16:04:09 -0600 Subject: [PATCH 2/4] Toggle line comments ignores last row of selection if it ends at col 0 Now that the cursor is hidden at the end of a selection, it's counter-intuitive for the commenting to extend to the next line since there's no visual indicator that the cursor extends to that location. --- spec/app/edit-session-spec.coffee | 8 ++++++++ spec/app/language-mode-spec.coffee | 12 ++++++------ src/app/edit-session.coffee | 4 ++-- src/app/language-mode.coffee | 9 ++++----- src/app/selection.coffee | 9 ++++++++- 5 files changed, 28 insertions(+), 14 deletions(-) diff --git a/spec/app/edit-session-spec.coffee b/spec/app/edit-session-spec.coffee index abb3d854f..b6e741e66 100644 --- a/spec/app/edit-session-spec.coffee +++ b/spec/app/edit-session-spec.coffee @@ -1489,6 +1489,14 @@ describe "EditSession", -> expect(buffer.lineForRow(6)).toBe " current < pivot ? left.push(current) : right.push(current);" expect(buffer.lineForRow(7)).toBe " }" + it "does not comment the last line of a non-empty selection if it ends at column 0", -> + editSession.setSelectedBufferRange([[4, 5], [7, 0]]) + editSession.toggleLineCommentsInSelection() + expect(buffer.lineForRow(4)).toBe "// while(items.length > 0) {" + expect(buffer.lineForRow(5)).toBe "// current = items.shift();" + expect(buffer.lineForRow(6)).toBe "// current < pivot ? left.push(current) : right.push(current);" + expect(buffer.lineForRow(7)).toBe " }" + it "uncomments lines if the first line matches the comment regex", -> editSession.setSelectedBufferRange([[4, 5], [4, 5]]) editSession.toggleLineCommentsInSelection() diff --git a/spec/app/language-mode-spec.coffee b/spec/app/language-mode-spec.coffee index 035d1419b..9d9429bc8 100644 --- a/spec/app/language-mode-spec.coffee +++ b/spec/app/language-mode-spec.coffee @@ -162,15 +162,15 @@ describe "LanguageMode", -> editSession = fixturesProject.buildEditSessionForPath('sample.js', autoIndent: false) { buffer, languageMode } = editSession - describe ".toggleLineCommentsInRange(range)", -> + describe ".toggleLineCommentsForBufferRows(start, end)", -> it "comments/uncomments lines in the given range", -> - languageMode.toggleLineCommentsInRange([[4, 5], [7, 8]]) + languageMode.toggleLineCommentsForBufferRows(4, 7) expect(buffer.lineForRow(4)).toBe "// while(items.length > 0) {" expect(buffer.lineForRow(5)).toBe "// current = items.shift();" expect(buffer.lineForRow(6)).toBe "// current < pivot ? left.push(current) : right.push(current);" expect(buffer.lineForRow(7)).toBe "// }" - languageMode.toggleLineCommentsInRange([[4, 5], [5, 8]]) + languageMode.toggleLineCommentsForBufferRows(4, 5) expect(buffer.lineForRow(4)).toBe " while(items.length > 0) {" expect(buffer.lineForRow(5)).toBe " current = items.shift();" expect(buffer.lineForRow(6)).toBe "// current < pivot ? left.push(current) : right.push(current);" @@ -204,15 +204,15 @@ describe "LanguageMode", -> editSession = fixturesProject.buildEditSessionForPath('coffee.coffee', autoIndent: false) { buffer, languageMode } = editSession - describe ".toggleLineCommentsInRange(range)", -> + describe ".toggleLineCommentsForBufferRows(start, end)", -> it "comments/uncomments lines in the given range", -> - languageMode.toggleLineCommentsInRange([[4, 5], [7, 8]]) + languageMode.toggleLineCommentsForBufferRows(4, 7) expect(buffer.lineForRow(4)).toBe "# pivot = items.shift()" expect(buffer.lineForRow(5)).toBe "# left = []" expect(buffer.lineForRow(6)).toBe "# right = []" expect(buffer.lineForRow(7)).toBe "# " - languageMode.toggleLineCommentsInRange([[4, 5], [5, 8]]) + languageMode.toggleLineCommentsForBufferRows(4, 5) expect(buffer.lineForRow(4)).toBe " pivot = items.shift()" expect(buffer.lineForRow(5)).toBe " left = []" expect(buffer.lineForRow(6)).toBe "# right = []" diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 7c94d928c..411afd421 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -286,8 +286,8 @@ class EditSession autoDecreaseIndentForRow: (bufferRow) -> @languageMode.autoDecreaseIndentForBufferRow(bufferRow) - toggleLineCommentsInRange: (range) -> - @languageMode.toggleLineCommentsInRange(range) + toggleLineCommentsForBufferRows: (start, end) -> + @languageMode.toggleLineCommentsForBufferRows(start, end) mutateSelectedText: (fn) -> @transact => fn(selection) for selection in @getSelections() diff --git a/src/app/language-mode.coffee b/src/app/language-mode.coffee index eae7a3f0f..4b98cc92a 100644 --- a/src/app/language-mode.coffee +++ b/src/app/language-mode.coffee @@ -64,18 +64,17 @@ class LanguageMode @invertedPairedCharacters[close] = open @invertedPairedCharacters - toggleLineCommentsInRange: (range) -> - range = Range.fromObject(range) - scopes = @getTokenizedBuffer().scopesForPosition(range.start) + toggleLineCommentsForBufferRows: (start, end) -> + scopes = @getTokenizedBuffer().scopesForPosition([start, 0]) return unless commentString = TextMateBundle.lineCommentStringForScope(scopes[0]) commentRegexString = _.escapeRegExp(commentString) commentRegexString = commentRegexString.replace(/(\s+)$/, '($1)?') commentRegex = new OnigRegExp("^\s*#{commentRegexString}") - shouldUncomment = commentRegex.test(@editSession.lineForBufferRow(range.start.row)) + shouldUncomment = commentRegex.test(@editSession.lineForBufferRow(start)) - for row in [range.start.row..range.end.row] + for row in [start..end] line = @editSession.lineForBufferRow(row) if shouldUncomment if match = commentRegex.search(line) diff --git a/src/app/selection.coffee b/src/app/selection.coffee index 3121fe678..6a19e5aa7 100644 --- a/src/app/selection.coffee +++ b/src/app/selection.coffee @@ -67,6 +67,13 @@ class Selection else new Range(@cursor.getBufferPosition(), @cursor.getBufferPosition()) + getBufferRowRange: -> + range = @getBufferRange() + start = range.start.row + end = range.end.row + end = Math.max(start, end - 1) if range.end.column == 0 + [start, end] + screenRangeChanged: -> screenRange = @getScreenRange() @trigger 'change-screen-range', screenRange @@ -272,7 +279,7 @@ class Selection toggleLineComments: -> @modifySelection => - @editSession.toggleLineCommentsInRange(@getBufferRange()) + @editSession.toggleLineCommentsForBufferRows(@getBufferRowRange()...) cutToEndOfLine: (maintainPasteboard) -> @selectToEndOfLine() if @isEmpty() From aa20fbac6d585ff647ba1321280502e4532c50fc Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 29 Oct 2012 16:16:19 -0600 Subject: [PATCH 3/4] Ignore an empty last line when indenting/outdenting selected lines This provides more intuitive behavior now that the cursor isn't visible when there's a selection. --- spec/app/edit-session-spec.coffee | 20 ++++++++++++++++++++ src/app/selection.coffee | 8 ++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/spec/app/edit-session-spec.coffee b/spec/app/edit-session-spec.coffee index b6e741e66..292eb28d9 100644 --- a/spec/app/edit-session-spec.coffee +++ b/spec/app/edit-session-spec.coffee @@ -1413,6 +1413,15 @@ describe "EditSession", -> expect(buffer.lineForRow(11)).toBe " return sort(Array.apply(this, arguments));" expect(editSession.getSelectedBufferRange()).toEqual [[9, 1 + editSession.tabLength], [11, 15 + editSession.tabLength]] + it "does not indent the last row if the selection ends at column 0", -> + editSession.tabLength = 2 + editSession.setSelectedBufferRange([[9,1], [11,0]]) + editSession.indentSelectedRows() + expect(buffer.lineForRow(9)).toBe " };" + expect(buffer.lineForRow(10)).toBe "" + expect(buffer.lineForRow(11)).toBe " return sort(Array.apply(this, arguments));" + expect(editSession.getSelectedBufferRange()).toEqual [[9, 1 + editSession.tabLength], [11, 0]] + describe "when softTabs is disabled", -> it "indents selected lines (that are not empty) and retains selection", -> convertToHardTabs(buffer) @@ -1470,8 +1479,19 @@ describe "EditSession", -> expect(buffer.lineForRow(0)).toBe "var quicksort = function () {" expect(buffer.lineForRow(1)).toBe "var sort = function(items) {" expect(buffer.lineForRow(2)).toBe " if (items.length <= 1) return items;" + expect(buffer.lineForRow(3)).toBe " var pivot = items.shift(), current, left = [], right = [];" expect(editSession.getSelectedBufferRange()).toEqual [[0, 1], [3, 15 - editSession.tabLength]] + it "does not outdent the last line of the selection if it ends at column 0", -> + editSession.setSelectedBufferRange([[0,1], [3,0]]) + editSession.outdentSelectedRows() + expect(buffer.lineForRow(0)).toBe "var quicksort = function () {" + expect(buffer.lineForRow(1)).toBe "var sort = function(items) {" + expect(buffer.lineForRow(2)).toBe " if (items.length <= 1) return items;" + expect(buffer.lineForRow(3)).toBe " var pivot = items.shift(), current, left = [], right = [];" + + expect(editSession.getSelectedBufferRange()).toEqual [[0, 1], [3, 0]] + describe ".toggleLineCommentsInSelection()", -> it "toggles comments on the selected lines", -> editSession.setSelectedBufferRange([[4, 5], [7, 5]]) diff --git a/src/app/selection.coffee b/src/app/selection.coffee index 6a19e5aa7..4759d1206 100644 --- a/src/app/selection.coffee +++ b/src/app/selection.coffee @@ -173,8 +173,8 @@ class Selection @indentSelectedRows() indentSelectedRows: -> - range = @getBufferRange() - for row in [range.start.row..range.end.row] + [start, end] = @getBufferRowRange() + for row in [start..end] @editSession.buffer.insert([row, 0], @editSession.getTabText()) unless @editSession.buffer.lineLengthForRow(row) == 0 normalizeIndent: (text, options) -> @@ -270,10 +270,10 @@ class Selection @editSession.buffer.deleteRows(start, end) outdentSelectedRows: -> - range = @getBufferRange() + [start, end] = @getBufferRowRange() buffer = @editSession.buffer leadingTabRegex = new RegExp("^ {1,#{@editSession.getTabLength()}}|\t") - for row in [range.start.row..range.end.row] + for row in [start..end] if matchLength = buffer.lineForRow(row).match(leadingTabRegex)?[0].length buffer.delete [[row, 0], [row, matchLength]] From 733264dc910193ea36067cfc58ff1d8d10ec6a50 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 29 Oct 2012 16:42:29 -0600 Subject: [PATCH 4/4] Don't highlight gutter lines when there's a multi-column selection --- spec/app/editor-spec.coffee | 5 ++--- src/app/gutter.coffee | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index cc1cfe814..64bcaa8a3 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -1657,12 +1657,11 @@ describe "Editor", -> beforeEach -> editor.attachToDom(30) - it "doesn't highlight the backround", -> + it "doesn't highlight the background or the gutter", -> editor.getSelection().setBufferRange(new Range([0,0],[2,0])) expect(editor.getSelection().isSingleScreenLine()).toBe false - expect(editor.find('.line-number.cursor-line-number').length).toBe 1 + expect(editor.find('.line-number.cursor-line-number').length).toBe 0 expect(editor.find('.line-number.cursor-line-number.cursor-line-number-background').length).toBe 0 - expect(editor.find('.line-number.cursor-line-number').text()).toBe "3" it "when a newline is deleted with backspace, the line number of the new cursor position is highlighted", -> editor.setCursorScreenPosition([1,0]) diff --git a/src/app/gutter.coffee b/src/app/gutter.coffee index 0361de79f..feeb6ea1b 100644 --- a/src/app/gutter.coffee +++ b/src/app/gutter.coffee @@ -66,7 +66,7 @@ class Gutter extends View currentLineNumberRow.removeClass('cursor-line-number') currentLineNumberRow.removeClass('cursor-line-number-background') - newLineNumberRow = @find(".line-number:eq(#{screenRowIndex})") - newLineNumberRow.addClass('cursor-line-number') if @editor().getSelection().isSingleScreenLine() + newLineNumberRow = @find(".line-number:eq(#{screenRowIndex})") + newLineNumberRow.addClass('cursor-line-number') newLineNumberRow.addClass('cursor-line-number-background')