From 0270ba3e1c7900e5fc0a620f09ccce655a7d27d6 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 4 Feb 2013 15:23:14 -0800 Subject: [PATCH 1/6] Add bracket matcher that highlights pair (), {}, and [] pairs are now highlighted when after or before the cursor --- src/app/edit-session.coffee | 3 + src/app/editor.coffee | 1 + src/packages/bracket-matcher/index.coffee | 94 +++++++++++++++++++ .../spec/bracket-matcher-spec.coffee | 60 ++++++++++++ .../stylesheets/bracket-matcher.css | 3 + themes/atom-dark-ui/bracket-matcher.css | 5 + themes/atom-dark-ui/package.json | 3 +- themes/atom-light-ui/bracket-matcher.css | 5 + themes/atom-light-ui/package.json | 3 +- 9 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 src/packages/bracket-matcher/index.coffee create mode 100644 src/packages/bracket-matcher/spec/bracket-matcher-spec.coffee create mode 100644 src/packages/bracket-matcher/stylesheets/bracket-matcher.css create mode 100644 themes/atom-dark-ui/bracket-matcher.css create mode 100644 themes/atom-light-ui/bracket-matcher.css diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index f6248c173..838a13b76 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -307,6 +307,9 @@ class EditSession fold.destroy() @setCursorBufferPosition([fold.startRow, 0]) + isFoldedAtCursorRow: -> + @isFoldedAtScreenRow(@getCursorScreenRow()) + isFoldedAtBufferRow: (bufferRow) -> screenRow = @screenPositionForBufferPosition([bufferRow]).row @isFoldedAtScreenRow(screenRow) diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 161e7505e..738f8269c 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -277,6 +277,7 @@ class Editor extends View destroyFoldsContainingBufferRow: (bufferRow) -> @activeEditSession.destroyFoldsContainingBufferRow(bufferRow) isFoldedAtScreenRow: (screenRow) -> @activeEditSession.isFoldedAtScreenRow(screenRow) isFoldedAtBufferRow: (bufferRow) -> @activeEditSession.isFoldedAtBufferRow(bufferRow) + isFoldedAtCursorRow: -> @activeEditSession.isFoldedAtCursorRow() lineForScreenRow: (screenRow) -> @activeEditSession.lineForScreenRow(screenRow) linesForScreenRows: (start, end) -> @activeEditSession.linesForScreenRows(start, end) diff --git a/src/packages/bracket-matcher/index.coffee b/src/packages/bracket-matcher/index.coffee new file mode 100644 index 000000000..aeb18f60c --- /dev/null +++ b/src/packages/bracket-matcher/index.coffee @@ -0,0 +1,94 @@ +AtomPackage = require 'atom-package' +_ = require 'underscore' +{$$} = require 'space-pen' +Range = require 'range' + +module.exports = +class BracketMatcher extends AtomPackage + startPairMatches: + '(': ')' + '[': ']' + '{': '}' + + endPairMatches: + ')': '(' + ']': '[' + '}': '{' + + pairHighlighted: false + + activate: (rootView) -> + rootView.eachEditor (editor) => @subscribeToEditor(editor) if editor.attached + + subscribeToEditor: (editor) -> + editor.on 'cursor:moved', => @updateMatch(editor) + + createView: (editor, bufferPosition) -> + pixelPosition = editor.pixelPositionForBufferPosition(bufferPosition) + view = $$ -> @div class: 'bracket-matcher' + view.css('top', pixelPosition.top).css('left', pixelPosition.left) + view.width(editor.charWidth).height(editor.charHeight) + + findCurrentPair: (editor, buffer, matches) -> + position = editor.getCursorBufferPosition() + currentPair = buffer.getTextInRange(Range.fromPointWithDelta(position, 0, 1)) + unless matches[currentPair] + position = position.translate([0, -1]) + currentPair = buffer.getTextInRange(Range.fromPointWithDelta(position, 0, 1)) + matchingPair = matches[currentPair] + if matchingPair + {position, currentPair, matchingPair} + else + {} + + findMatchingEndPair: (buffer, startPairPosition, startPair, endPair) -> + scanRange = new Range(startPairPosition.translate([0, 1]), buffer.getEofPosition()) + regex = new RegExp("[#{_.escapeRegExp(startPair + endPair)}]", 'g') + endPairPosition = null + unpairedCount = 0 + buffer.scanInRange regex, scanRange, (match, range, {stop}) => + if match[0] is startPair + unpairedCount++ + else if match[0] is endPair + unpairedCount-- + endPairPosition = range.start + stop() if unpairedCount < 0 + endPairPosition + + findMatchingStartPair: (buffer, endPairPosition, startPair, endPair) -> + scanRange = new Range([0, 0], endPairPosition) + regex = new RegExp("[#{_.escapeRegExp(startPair + endPair)}]", 'g') + startPairPosition = null + unpairedCount = 0 + scanner = (match, range, {stop}) => + if match[0] is endPair + unpairedCount++ + else if match[0] is startPair + unpairedCount-- + startPairPosition = range.start + stop() if unpairedCount < 0 + buffer.scanInRange(regex, scanRange, scanner, true) + startPairPosition + + updateMatch: (editor) -> + return unless underlayer = editor.pane()?.find('.underlayer') + + underlayer.find('.bracket-matcher').remove() if @pairHighlighted + @pairHighlighted = false + + return unless editor.getSelection().isEmpty() + return if editor.isFoldedAtCursorRow() + + buffer = editor.getBuffer() + {position, currentPair, matchingPair} = @findCurrentPair(editor, buffer, @startPairMatches) + if position + matchPosition = @findMatchingEndPair(buffer, position, currentPair, matchingPair) + else + {position, currentPair, matchingPair} = @findCurrentPair(editor, buffer, @endPairMatches) + if position + matchPosition = @findMatchingStartPair(buffer, position, matchingPair, currentPair) + + if position? and matchPosition? + underlayer.append(@createView(editor, position)) + underlayer.append(@createView(editor, matchPosition)) + @pairHighlighted = true diff --git a/src/packages/bracket-matcher/spec/bracket-matcher-spec.coffee b/src/packages/bracket-matcher/spec/bracket-matcher-spec.coffee new file mode 100644 index 000000000..8b1e1708d --- /dev/null +++ b/src/packages/bracket-matcher/spec/bracket-matcher-spec.coffee @@ -0,0 +1,60 @@ +RootView = require 'root-view' + +describe "bracket matching", -> + [rootView, editor] = [] + + beforeEach -> + rootView = new RootView(require.resolve('fixtures/sample.js')) + atom.loadPackage('bracket-matcher') + rootView.attachToDom() + editor = rootView.getActiveEditor() + + afterEach -> + rootView.deactivate() + + describe "when the cursor is before a starting pair", -> + it "highlights the starting pair and ending pair", -> + editor.moveCursorToEndOfLine() + editor.moveCursorLeft() + expect(editor.underlayer.find('.bracket-matcher').length).toBe 2 + expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([0,28]) + expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([12,0]) + + describe "when the cursor is after a starting pair", -> + it "highlights the starting pair and ending pair", -> + editor.moveCursorToEndOfLine() + expect(editor.underlayer.find('.bracket-matcher').length).toBe 2 + expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([0,28]) + expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([12,0]) + + describe "when the cursor is before an ending pair", -> + it "highlights the starting pair and ending pair", -> + editor.moveCursorToBottom() + editor.moveCursorLeft() + editor.moveCursorLeft() + expect(editor.underlayer.find('.bracket-matcher').length).toBe 2 + expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([12,0]) + expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([0,28]) + + describe "when the cursor is after an ending pair", -> + it "highlights the starting pair and ending pair", -> + editor.moveCursorToBottom() + editor.moveCursorLeft() + expect(editor.underlayer.find('.bracket-matcher').length).toBe 2 + expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([12,0]) + expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([0,28]) + + describe "when the cursor is moved off a pair", -> + it "removes the starting pair and ending pair highlights", -> + editor.moveCursorToEndOfLine() + expect(editor.underlayer.find('.bracket-matcher').length).toBe 2 + editor.moveCursorToBeginningOfLine() + expect(editor.underlayer.find('.bracket-matcher').length).toBe 0 + + describe "pair balancing", -> + describe "when a second starting pair preceeds the first ending pair", -> + it "advances to the second ending pair", -> + editor.setCursorBufferPosition([8,42]) + expect(editor.underlayer.find('.bracket-matcher').length).toBe 2 + expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([8,42]) + expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([8,54]) diff --git a/src/packages/bracket-matcher/stylesheets/bracket-matcher.css b/src/packages/bracket-matcher/stylesheets/bracket-matcher.css new file mode 100644 index 000000000..8d77b10de --- /dev/null +++ b/src/packages/bracket-matcher/stylesheets/bracket-matcher.css @@ -0,0 +1,3 @@ +.bracket-matcher { + position: absolute; +} diff --git a/themes/atom-dark-ui/bracket-matcher.css b/themes/atom-dark-ui/bracket-matcher.css new file mode 100644 index 000000000..d579fdc35 --- /dev/null +++ b/themes/atom-dark-ui/bracket-matcher.css @@ -0,0 +1,5 @@ +.bracket-matcher { + border-bottom: 1px solid #f8de7e; + margin-top: -1px; + opacity: .7; +} diff --git a/themes/atom-dark-ui/package.json b/themes/atom-dark-ui/package.json index d0a007f7f..44dd4534d 100644 --- a/themes/atom-dark-ui/package.json +++ b/themes/atom-dark-ui/package.json @@ -8,6 +8,7 @@ "status-bar.css", "markdown-preview.css", "command-panel.css", - "command-logger.css" + "command-logger.css", + "bracket-matcher.css" ] } diff --git a/themes/atom-light-ui/bracket-matcher.css b/themes/atom-light-ui/bracket-matcher.css new file mode 100644 index 000000000..d579fdc35 --- /dev/null +++ b/themes/atom-light-ui/bracket-matcher.css @@ -0,0 +1,5 @@ +.bracket-matcher { + border-bottom: 1px solid #f8de7e; + margin-top: -1px; + opacity: .7; +} diff --git a/themes/atom-light-ui/package.json b/themes/atom-light-ui/package.json index d0a007f7f..44dd4534d 100644 --- a/themes/atom-light-ui/package.json +++ b/themes/atom-light-ui/package.json @@ -8,6 +8,7 @@ "status-bar.css", "markdown-preview.css", "command-panel.css", - "command-logger.css" + "command-logger.css", + "bracket-matcher.css" ] } From b30fae7a682f4ba228b6d6381322c8c5e5e43366 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 4 Feb 2013 18:24:54 -0800 Subject: [PATCH 2/6] Remove event handlers when editor is removed --- src/packages/bracket-matcher/index.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/packages/bracket-matcher/index.coffee b/src/packages/bracket-matcher/index.coffee index aeb18f60c..95a3ff8a8 100644 --- a/src/packages/bracket-matcher/index.coffee +++ b/src/packages/bracket-matcher/index.coffee @@ -21,7 +21,8 @@ class BracketMatcher extends AtomPackage rootView.eachEditor (editor) => @subscribeToEditor(editor) if editor.attached subscribeToEditor: (editor) -> - editor.on 'cursor:moved', => @updateMatch(editor) + editor.on 'cursor:moved.bracket-matcher', => @updateMatch(editor) + editor.on 'editor:will-be-removed', => editor.off('.bracket-matcher') createView: (editor, bufferPosition) -> pixelPosition = editor.pixelPositionForBufferPosition(bufferPosition) From 39e39afa1b15c5bcde718b14ba403944dc9fc743 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 4 Feb 2013 20:38:15 -0800 Subject: [PATCH 3/6] Bind ctrl-j to jump to matching bracket --- src/packages/bracket-matcher/index.coffee | 28 +++++++++++++-- .../keymaps/bracket-matcher.cson | 2 ++ .../spec/bracket-matcher-spec.coffee | 34 ++++++++++++++++--- 3 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 src/packages/bracket-matcher/keymaps/bracket-matcher.cson diff --git a/src/packages/bracket-matcher/index.coffee b/src/packages/bracket-matcher/index.coffee index 95a3ff8a8..783868c5d 100644 --- a/src/packages/bracket-matcher/index.coffee +++ b/src/packages/bracket-matcher/index.coffee @@ -22,11 +22,31 @@ class BracketMatcher extends AtomPackage subscribeToEditor: (editor) -> editor.on 'cursor:moved.bracket-matcher', => @updateMatch(editor) + editor.command 'editor:go-to-matching-bracket', => @goToMatchingPair(editor) editor.on 'editor:will-be-removed', => editor.off('.bracket-matcher') + goToMatchingPair: (editor) -> + return unless @pairHighlighted + return unless underlayer = editor.pane()?.find('.underlayer') + + position = editor.getCursorBufferPosition() + previousPosition = position.translate([0, -1]) + startPosition = underlayer.find('.bracket-matcher:first').data('bufferPosition') + endPosition = underlayer.find('.bracket-matcher:last').data('bufferPosition') + + if position.isEqual(startPosition) + editor.setCursorBufferPosition(endPosition.translate([0, 1])) + else if previousPosition.isEqual(startPosition) + editor.setCursorBufferPosition(endPosition) + else if position.isEqual(endPosition) + editor.setCursorBufferPosition(startPosition.translate([0, 1])) + else if previousPosition.isEqual(endPosition) + editor.setCursorBufferPosition(startPosition) + createView: (editor, bufferPosition) -> pixelPosition = editor.pixelPositionForBufferPosition(bufferPosition) view = $$ -> @div class: 'bracket-matcher' + view.data('bufferPosition', bufferPosition) view.css('top', pixelPosition.top).css('left', pixelPosition.left) view.width(editor.charWidth).height(editor.charHeight) @@ -90,6 +110,10 @@ class BracketMatcher extends AtomPackage matchPosition = @findMatchingStartPair(buffer, position, matchingPair, currentPair) if position? and matchPosition? - underlayer.append(@createView(editor, position)) - underlayer.append(@createView(editor, matchPosition)) + if position.isLessThan(matchPosition) + underlayer.append(@createView(editor, position)) + underlayer.append(@createView(editor, matchPosition)) + else + underlayer.append(@createView(editor, matchPosition)) + underlayer.append(@createView(editor, position)) @pairHighlighted = true diff --git a/src/packages/bracket-matcher/keymaps/bracket-matcher.cson b/src/packages/bracket-matcher/keymaps/bracket-matcher.cson new file mode 100644 index 000000000..672df9be6 --- /dev/null +++ b/src/packages/bracket-matcher/keymaps/bracket-matcher.cson @@ -0,0 +1,2 @@ +'.editor': + 'ctrl-j': 'editor:go-to-matching-bracket' diff --git a/src/packages/bracket-matcher/spec/bracket-matcher-spec.coffee b/src/packages/bracket-matcher/spec/bracket-matcher-spec.coffee index 8b1e1708d..69e882502 100644 --- a/src/packages/bracket-matcher/spec/bracket-matcher-spec.coffee +++ b/src/packages/bracket-matcher/spec/bracket-matcher-spec.coffee @@ -33,16 +33,16 @@ describe "bracket matching", -> editor.moveCursorLeft() editor.moveCursorLeft() expect(editor.underlayer.find('.bracket-matcher').length).toBe 2 - expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([12,0]) - expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([0,28]) + expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([12,0]) + expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([0,28]) describe "when the cursor is after an ending pair", -> it "highlights the starting pair and ending pair", -> editor.moveCursorToBottom() editor.moveCursorLeft() expect(editor.underlayer.find('.bracket-matcher').length).toBe 2 - expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([12,0]) - expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([0,28]) + expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([12,0]) + expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([0,28]) describe "when the cursor is moved off a pair", -> it "removes the starting pair and ending pair highlights", -> @@ -58,3 +58,29 @@ describe "bracket matching", -> expect(editor.underlayer.find('.bracket-matcher').length).toBe 2 expect(editor.underlayer.find('.bracket-matcher:first').position()).toEqual editor.pixelPositionForBufferPosition([8,42]) expect(editor.underlayer.find('.bracket-matcher:last').position()).toEqual editor.pixelPositionForBufferPosition([8,54]) + + describe "when editor:go-to-matching-bracket is triggered", -> + describe "when the cursor is before the starting pair", -> + it "moves the cursor to after the ending pair", -> + editor.moveCursorToEndOfLine() + editor.moveCursorLeft() + editor.trigger "editor:go-to-matching-bracket" + expect(editor.getCursorBufferPosition()).toEqual [12, 1] + + describe "when the cursor is after the starting pair", -> + it "moves the cursor to before the ending pair", -> + editor.moveCursorToEndOfLine() + editor.trigger "editor:go-to-matching-bracket" + expect(editor.getCursorBufferPosition()).toEqual [12, 0] + + describe "when the cursor is before the ending pair", -> + it "moves the cursor to after the starting pair", -> + editor.setCursorBufferPosition([12, 0]) + editor.trigger "editor:go-to-matching-bracket" + expect(editor.getCursorBufferPosition()).toEqual [0, 29] + + describe "when the cursor is after the ending pair", -> + it "moves the cursor to before the starting pair", -> + editor.setCursorBufferPosition([12, 1]) + editor.trigger "editor:go-to-matching-bracket" + expect(editor.getCursorBufferPosition()).toEqual [0, 28] From b0b38079e0f169fb730d73339c6e771434511814 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 4 Feb 2013 20:50:14 -0800 Subject: [PATCH 4/6] Migrate template config file to cson --- .atom/config.cson | 7 +++++++ .atom/config.json | 11 ----------- 2 files changed, 7 insertions(+), 11 deletions(-) create mode 100644 .atom/config.cson delete mode 100644 .atom/config.json diff --git a/.atom/config.cson b/.atom/config.cson new file mode 100644 index 000000000..a93b83e66 --- /dev/null +++ b/.atom/config.cson @@ -0,0 +1,7 @@ +'editor': + 'fontSize': 16 +'core': + 'themes': [ + 'atom-dark-ui' + 'atom-dark-syntax' + ] diff --git a/.atom/config.json b/.atom/config.json deleted file mode 100644 index 50770e810..000000000 --- a/.atom/config.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "editor": { - "fontSize": 16 - }, - "core": { - "themes": [ - "atom-dark-ui", - "atom-dark-syntax" - ] - } -} From 651d305f761ace9e40b38f14b84f04c339d87acd Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 4 Feb 2013 20:56:25 -0800 Subject: [PATCH 5/6] Add package namespace to command registration --- src/packages/bracket-matcher/index.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/packages/bracket-matcher/index.coffee b/src/packages/bracket-matcher/index.coffee index 783868c5d..081ca9923 100644 --- a/src/packages/bracket-matcher/index.coffee +++ b/src/packages/bracket-matcher/index.coffee @@ -22,7 +22,8 @@ class BracketMatcher extends AtomPackage subscribeToEditor: (editor) -> editor.on 'cursor:moved.bracket-matcher', => @updateMatch(editor) - editor.command 'editor:go-to-matching-bracket', => @goToMatchingPair(editor) + editor.command 'editor:go-to-matching-bracket.bracket-matcher', => + @goToMatchingPair(editor) editor.on 'editor:will-be-removed', => editor.off('.bracket-matcher') goToMatchingPair: (editor) -> From 3514048893579f7ec49f599d344418f5d550a379 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 4 Feb 2013 21:20:17 -0800 Subject: [PATCH 6/6] Exclude background window from expose Previously you would see an untitled empty space when showing application windows with ctrl-down. --- native/atom_window_controller.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/native/atom_window_controller.mm b/native/atom_window_controller.mm index d61e46063..4a26e9601 100644 --- a/native/atom_window_controller.mm +++ b/native/atom_window_controller.mm @@ -69,6 +69,7 @@ [self initWithBootstrapScript:@"window-bootstrap" background:YES alwaysUseBundleResourcePath:stable]; [self.window setFrame:NSMakeRect(0, 0, 0, 0) display:NO]; [self.window setExcludedFromWindowsMenu:YES]; + [self.window setCollectionBehavior:NSWindowCollectionBehaviorStationary]; return self; }