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" - ] - } -} diff --git a/.atom/packages/README.md b/.atom/packages/README.md new file mode 100644 index 000000000..540b6949c --- /dev/null +++ b/.atom/packages/README.md @@ -0,0 +1 @@ +All packages in this directory will be automatically loaded diff --git a/.pairs b/.pairs index c939edd3c..ec4f2417c 100644 --- a/.pairs +++ b/.pairs @@ -5,6 +5,7 @@ pairs: ks: Kevin Sawicki; kevin jc: Jerry Cheung; jerry bl: Brian Lopez; brian + jp: Justin Palmer; justin email: domain: github.com #global: true diff --git a/docs/styling.md b/docs/styling.md index 847df3fbb..47a3795ae 100644 --- a/docs/styling.md +++ b/docs/styling.md @@ -9,8 +9,8 @@ gutter. You can change the background color using the following CSS: ```css -.editor.is-focused .line.cursor-line, -.editor.is-focused .line-number.cursor-line { +.editor .line.cursor-line, +.editor .line-number.cursor-line { background-color: green; } ``` @@ -18,7 +18,7 @@ You can change the background color using the following CSS: You can change the line number foreground color using the following CSS: ```css -.editor.is-focused .line-number.cursor-line { +.editor .line-number.cursor-line { color: blue; } ``` diff --git a/native/atom_cef_client.cpp b/native/atom_cef_client.cpp index 3acb53951..27badf291 100644 --- a/native/atom_cef_client.cpp +++ b/native/atom_cef_client.cpp @@ -80,6 +80,9 @@ bool AtomCefClient::OnProcessMessageReceived(CefRefPtr browser, else if (name == "show") { Show(browser); } + else if (name == "toggleFullScreen") { + ToggleFullScreen(browser); + } else { return false; } diff --git a/native/atom_cef_client.h b/native/atom_cef_client.h index d1961d8d3..85d9a031b 100644 --- a/native/atom_cef_client.h +++ b/native/atom_cef_client.h @@ -124,6 +124,7 @@ class AtomCefClient : public CefClient, void Exit(int status); void Log(const char *message); void Show(CefRefPtr browser); + void ToggleFullScreen(CefRefPtr browser); IMPLEMENT_REFCOUNTING(AtomCefClient); IMPLEMENT_LOCKING(AtomCefClient); diff --git a/native/atom_cef_client_mac.mm b/native/atom_cef_client_mac.mm index be4879d97..d2f250ec0 100644 --- a/native/atom_cef_client_mac.mm +++ b/native/atom_cef_client_mac.mm @@ -118,6 +118,10 @@ void AtomCefClient::Show(CefRefPtr browser) { [windowController.webView setHidden:NO]; } +void AtomCefClient::ToggleFullScreen(CefRefPtr browser) { + [[browser->GetHost()->GetWindowHandle() window] toggleFullScreen:nil]; +} + void AtomCefClient::ShowSaveDialog(int replyId, CefRefPtr browser) { CefRefPtr replyMessage = CefProcessMessage::Create("reply"); CefRefPtr replyArguments = replyMessage->GetArgumentList(); 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; } diff --git a/spec/app/buffer-spec.coffee b/spec/app/buffer-spec.coffee index deeb6d608..f90802175 100644 --- a/spec/app/buffer-spec.coffee +++ b/spec/app/buffer-spec.coffee @@ -1159,3 +1159,17 @@ describe 'Buffer', -> buffer.setText("\ninitialtext") buffer.append("hello\n1\r\n2\n") expect(buffer.getText()).toBe "\ninitialtexthello\n1\n2\n" + + describe ".clipPosition(position)", -> + describe "when the position is before the start of the buffer", -> + it "returns the first position in the buffer", -> + expect(buffer.clipPosition([-1,0])).toEqual [0,0] + expect(buffer.clipPosition([0,-1])).toEqual [0,0] + expect(buffer.clipPosition([-1,-1])).toEqual [0,0] + + describe "when the position is after the end of the buffer", -> + it "returns the last position in the buffer", -> + buffer.setText('some text') + expect(buffer.clipPosition([1, 0])).toEqual [0,9] + expect(buffer.clipPosition([0,10])).toEqual [0,9] + expect(buffer.clipPosition([10,Infinity])).toEqual [0,9] diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index 3e5aa7f7c..c52ad8022 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -111,11 +111,9 @@ describe "Editor", -> editor.isFocused = false editor.hiddenInput.focus() expect(editor.isFocused).toBeTruthy() - expect(editor).toHaveClass('is-focused') editor.hiddenInput.focusout() expect(editor.isFocused).toBeFalsy() - expect(editor).not.toHaveClass('is-focused') describe "when the activeEditSession's file is modified on disk", -> it "triggers an alert", -> @@ -2544,3 +2542,46 @@ describe "Editor", -> editor.trigger 'editor:move-line-down' expect(buffer.lineForRow(1)).toBe ' if (items.length <= 1) return items;' expect(buffer.lineForRow(2)).toBe ' var sort = function(items) {' + + describe "when editor:duplicate-line is triggered", -> + describe "where there is no selection", -> + describe "when the cursor isn't on a folded line", -> + it "duplicates the current line below and moves the cursor down one row", -> + editor.setCursorBufferPosition([0, 5]) + editor.trigger 'editor:duplicate-line' + expect(buffer.lineForRow(0)).toBe 'var quicksort = function () {' + expect(buffer.lineForRow(1)).toBe 'var quicksort = function () {' + expect(editor.getCursorBufferPosition()).toEqual [1, 5] + + describe "when the cursor is on a folded line", -> + it "duplicates the entire fold before and moves the cursor to the new fold", -> + editor.setCursorBufferPosition([4]) + editor.foldCurrentRow() + editor.trigger 'editor:duplicate-line' + expect(editor.getCursorScreenPosition()).toEqual [5] + expect(editor.isFoldedAtScreenRow(4)).toBeTruthy() + expect(editor.isFoldedAtScreenRow(5)).toBeTruthy() + expect(buffer.lineForRow(8)).toBe ' while(items.length > 0) {' + expect(buffer.lineForRow(9)).toBe ' current = items.shift();' + expect(buffer.lineForRow(10)).toBe ' current < pivot ? left.push(current) : right.push(current);' + expect(buffer.lineForRow(11)).toBe ' }' + + describe "when the cursor is on the last line and it doesn't have a trailing newline", -> + it "inserts a newline and the duplicated line", -> + editor.moveCursorToBottom() + editor.trigger 'editor:duplicate-line' + expect(buffer.lineForRow(12)).toBe '};' + expect(buffer.lineForRow(13)).toBe '};' + expect(buffer.lineForRow(14)).toBeUndefined() + expect(editor.getCursorBufferPosition()).toEqual [13, 2] + + describe "when the cursor in on the last line and it is only a newline", -> + it "duplicates the current line below and moves the cursor down one row", -> + editor.moveCursorToBottom() + editor.insertNewline() + editor.moveCursorToBottom() + editor.trigger 'editor:duplicate-line' + expect(buffer.lineForRow(13)).toBe '' + expect(buffer.lineForRow(14)).toBe '' + expect(buffer.lineForRow(15)).toBeUndefined() + expect(editor.getCursorBufferPosition()).toEqual [14, 0] diff --git a/spec/app/window-spec.coffee b/spec/app/window-spec.coffee index 5cca06198..1c166db8f 100644 --- a/spec/app/window-spec.coffee +++ b/spec/app/window-spec.coffee @@ -5,6 +5,7 @@ describe "Window", -> [rootView] = [] beforeEach -> + window.setUpEventHandlers() window.attachRootView(require.resolve('fixtures')) rootView = window.rootView @@ -13,13 +14,24 @@ describe "Window", -> atom.setRootViewStateForPath(rootView.project.getPath(), null) $(window).off 'beforeunload' - describe "window is loaded", -> - it "has .is-focused on the body tag", -> - expect($("body").hasClass("is-focused")).toBe true + describe "when the window is loaded", -> + it "doesn't have .is-blurred on the body tag", -> + expect($("body")).not.toHaveClass("is-blurred") - it "doesn't have .is-focused on the window blur event", -> - $(window).blur() - expect($("body").hasClass("is-focused")).toBe false + describe "when the window is blurred", -> + beforeEach -> + $(window).trigger 'blur' + + afterEach -> + $('body').removeClass('is-blurred') + + it "adds the .is-blurred class on the body", -> + expect($("body")).toHaveClass("is-blurred") + + describe "when the window is focused again", -> + it "removes the .is-blurred class from the body", -> + $(window).trigger 'focus' + expect($("body")).not.toHaveClass("is-blurred") describe ".close()", -> it "is triggered by the 'core:close' event", -> diff --git a/src/app/atom.coffee b/src/app/atom.coffee index e5eb0197f..b080e03bb 100644 --- a/src/app/atom.coffee +++ b/src/app/atom.coffee @@ -111,6 +111,9 @@ _.extend atom, endTracing: -> @sendMessageToBrowserProcess('endTracing') + toggleFullScreen: -> + @sendMessageToBrowserProcess('toggleFullScreen') + getRootViewStateForPath: (path) -> if json = localStorage[path] JSON.parse(json) diff --git a/src/app/buffer.coffee b/src/app/buffer.coffee index 5838f5e63..409559e52 100644 --- a/src/app/buffer.coffee +++ b/src/app/buffer.coffee @@ -218,13 +218,15 @@ class Buffer range clipPosition: (position) -> - { row, column } = Point.fromObject(position) - row = 0 if row < 0 - column = 0 if column < 0 - row = Math.min(@getLastRow(), row) - column = Math.min(@lineLengthForRow(row), column) - - new Point(row, column) + position = Point.fromObject(position) + eofPosition = @getEofPosition() + if position.isGreaterThan(eofPosition) + eofPosition + else + row = Math.max(position.row, 0) + column = Math.max(position.column, 0) + column = Math.min(@lineLengthForRow(row), column) + new Point(row, column) clipRange: (range) -> range = Range.fromObject(range) diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index bad4b92b8..53cb196a3 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) @@ -408,6 +411,28 @@ class EditSession @setSelectedBufferRange(selection.translate([1]), preserveFolds: true) + duplicateLine: -> + return unless @getSelection().isEmpty() + + @transact => + cursorPosition = @getCursorBufferPosition() + cursorRowFolded = @isFoldedAtCursorRow() + if cursorRowFolded + screenRow = @screenPositionForBufferPosition(cursorPosition).row + bufferRange = @bufferRangeForScreenRange([[screenRow], [screenRow + 1]]) + else + bufferRange = new Range([cursorPosition.row], [cursorPosition.row + 1]) + + insertPosition = new Point(bufferRange.end.row) + if insertPosition.row >= @buffer.getLastRow() + @unfoldCurrentRow() if cursorRowFolded + @buffer.append("\n#{@getTextInBufferRange(bufferRange)}") + @foldCurrentRow() if cursorRowFolded + else + @buffer.insert(insertPosition, @getTextInBufferRange(bufferRange)) + + @setCursorScreenPosition(@getCursorScreenPosition().translate([1])) + @foldCurrentRow() if cursorRowFolded mutateSelectedText: (fn) -> @transact => fn(selection) for selection in @getSelections() diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 1c0782304..b8f51e657 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -186,6 +186,7 @@ class Editor extends View 'editor:copy-path': @copyPathToPasteboard 'editor:move-line-up': @moveLineUp 'editor:move-line-down': @moveLineDown + 'editor:duplicate-line': @duplicateLine documentation = {} for name, method of editorBindings @@ -210,6 +211,7 @@ class Editor extends View moveLineUp: -> @activeEditSession.moveLineUp() moveLineDown: -> @activeEditSession.moveLineDown() setCursorScreenPosition: (position, options) -> @activeEditSession.setCursorScreenPosition(position, options) + duplicateLine: -> @activeEditSession.duplicateLine() getCursorScreenPosition: -> @activeEditSession.getCursorScreenPosition() getCursorScreenRow: -> @activeEditSession.getCursorScreenRow() setCursorBufferPosition: (position, options) -> @activeEditSession.setCursorBufferPosition(position, options) @@ -277,6 +279,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) @@ -355,8 +358,8 @@ class Editor extends View @hiddenInput.on 'focusout', => @isFocused = false - @removeClass 'is-focused' @autosave() if config.get "editor.autosave" + @removeClass 'is-focused' @underlayer.on 'click', (e) => return unless e.target is @underlayer[0] diff --git a/src/app/keymaps/atom.cson b/src/app/keymaps/atom.cson index 97c080d26..2d1d6c01e 100644 --- a/src/app/keymaps/atom.cson +++ b/src/app/keymaps/atom.cson @@ -28,6 +28,7 @@ 'meta--': 'window:decrease-font-size' 'ctrl-w w': 'window:focus-next-pane' 'ctrl-tab': 'window:focus-next-pane' + 'ctrl-meta-f': 'window:toggle-full-screen' 'alt-meta-i': 'toggle-dev-tools' diff --git a/src/app/keymaps/editor.cson b/src/app/keymaps/editor.cson index 4c977b698..fdccfd939 100644 --- a/src/app/keymaps/editor.cson +++ b/src/app/keymaps/editor.cson @@ -40,3 +40,4 @@ 'ctrl-C': 'editor:copy-path' 'ctrl-meta-up': 'editor:move-line-up' 'ctrl-meta-down': 'editor:move-line-down' + 'meta-D': 'editor:duplicate-line' diff --git a/src/app/window.coffee b/src/app/window.coffee index 998f381a4..15f7c319e 100644 --- a/src/app/window.coffee +++ b/src/app/window.coffee @@ -25,11 +25,14 @@ windowAdditions = @syntax = new Syntax @setUpKeymap() @pasteboard = new Pasteboard + @setUpEventHandlers() + setUpEventHandlers: -> $(window).on 'core:close', => @close() $(window).command 'window:close', => @close() - $(window).on 'focus', => $("body").addClass("is-focused") - $(window).on 'blur', => $("body").removeClass("is-focused") + $(window).command 'window:toggle-full-screen', => atom.toggleFullScreen() + $(window).on 'focus', -> $("body").removeClass('is-blurred') + $(window).on 'blur', -> $("body").addClass('is-blurred') # This method is intended only to be run when starting a normal application # Note: RootView assigns itself on window on initialization so that @@ -51,8 +54,8 @@ windowAdditions = atom.setWindowState('pathToOpen', @rootView.project.getPath()) @rootView.deactivate() @rootView = null - $(window).unbind('focus') - $(window).unbind('blur') + $(window).off('focus') + $(window).off('blur') $(window).off('before') setUpKeymap: -> diff --git a/src/packages/bracket-matcher/index.coffee b/src/packages/bracket-matcher/index.coffee new file mode 100644 index 000000000..081ca9923 --- /dev/null +++ b/src/packages/bracket-matcher/index.coffee @@ -0,0 +1,120 @@ +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.bracket-matcher', => @updateMatch(editor) + editor.command 'editor:go-to-matching-bracket.bracket-matcher', => + @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) + + 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? + 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 new file mode 100644 index 000000000..69e882502 --- /dev/null +++ b/src/packages/bracket-matcher/spec/bracket-matcher-spec.coffee @@ -0,0 +1,86 @@ +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: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: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", -> + 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]) + + 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] 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/src/packages/command-panel/src/preview-list.coffee b/src/packages/command-panel/src/preview-list.coffee index cb0296d46..364a6c5b6 100644 --- a/src/packages/command-panel/src/preview-list.coffee +++ b/src/packages/command-panel/src/preview-list.coffee @@ -2,6 +2,7 @@ $ = require 'jquery' {$$$} = require 'space-pen' ScrollView = require 'scroll-view' _ = require 'underscore' +fs = require 'fs' module.exports = class PreviewList extends ScrollView @@ -34,7 +35,9 @@ class PreviewList extends ScrollView operation.index = index for operation, index in operations operationsByPath = _.groupBy(operations, (operation) -> operation.getPath()) for path, ops of operationsByPath - @li class: 'path', => + classes = ['path'] + classes.push('readme') if fs.isReadme(path) + @li class: classes.join(' '), => @span path @span "(#{ops.length})", class: 'path-match-number' for operation in ops diff --git a/src/packages/fuzzy-finder/src/fuzzy-finder-view.coffee b/src/packages/fuzzy-finder/src/fuzzy-finder-view.coffee index 85c2c57d1..17165a8c1 100644 --- a/src/packages/fuzzy-finder/src/fuzzy-finder-view.coffee +++ b/src/packages/fuzzy-finder/src/fuzzy-finder-view.coffee @@ -39,7 +39,9 @@ class FuzzyFinderView extends SelectList $$ -> @li => ext = fs.extension(path) - if fs.isCompressedExtension(ext) + if fs.isReadme(path) + typeClass = 'readme-name' + else if fs.isCompressedExtension(ext) typeClass = 'compressed-name' else if fs.isImageExtension(ext) typeClass = 'image-name' diff --git a/src/packages/symbols-view/src/tag-generator.coffee b/src/packages/symbols-view/src/tag-generator.coffee index bcc958bb4..7517d4dea 100644 --- a/src/packages/symbols-view/src/tag-generator.coffee +++ b/src/packages/symbols-view/src/tag-generator.coffee @@ -6,40 +6,13 @@ class TagGenerator constructor: (@path, @callback) -> - parsePrefix: (section = "") -> - if section.indexOf('class:') is 0 - section.substring(6) - else if section.indexOf('namespace:') is 0 - section.substring(10) - else if section.indexOf('file:') is 0 - section.substring(5) - else if section.indexOf('signature:') is 0 - section.substring(10) - else if section.indexOf('struct:') is 0 - section.substring(7) - else - section - parseTagLine: (line) -> sections = line.split('\t') - return null if sections.length < 4 - - label = sections[0] - line = parseInt(sections[2]) - 1 - if sections.length > 5 - if prefix = @parsePrefix(sections[4]) - label = "#{prefix}::#{label}" - if suffix = @parsePrefix(sections[5]) - label = "#{label}#{suffix}" + if sections.length > 3 + position: new Point(parseInt(sections[2]) - 1) + name: sections[0] else - if suffix = @parsePrefix(sections[4]) - label = "#{label}#{suffix}" - - tag = - position: new Point(line, 0) - name: label - - return tag + null generate: -> options = diff --git a/src/packages/tree-view/src/file-view.coffee b/src/packages/tree-view/src/file-view.coffee index ea75356da..142bc5eea 100644 --- a/src/packages/tree-view/src/file-view.coffee +++ b/src/packages/tree-view/src/file-view.coffee @@ -17,7 +17,9 @@ class FileView extends View @subscribe $(window), 'focus', => @updateStatus() extension = fs.extension(@getPath()) - if fs.isCompressedExtension(extension) + if fs.isReadme(@getPath()) + @fileName.addClass('readme-icon') + else if fs.isCompressedExtension(extension) @fileName.addClass('compressed-icon') else if fs.isImageExtension(extension) @fileName.addClass('image-icon') diff --git a/src/stdlib/fs.coffee b/src/stdlib/fs.coffee index 9225913ec..35e9c5510 100644 --- a/src/stdlib/fs.coffee +++ b/src/stdlib/fs.coffee @@ -180,6 +180,12 @@ module.exports = '.ron' ], ext, true) >= 0 + + isReadme: (path) -> + extension = @extension(path) + base = @base(path, extension).toLowerCase() + base is 'readme' and (extension is '' or @isMarkdownExtension(extension)) + readObject: (path) -> contents = @read(path) if @extension(path) is '.cson' diff --git a/static/command-panel.css b/static/command-panel.css index aa6796897..5f10228c4 100644 --- a/static/command-panel.css +++ b/static/command-panel.css @@ -32,6 +32,10 @@ top: 1px; } +.command-panel .preview-list .path.readme:before { + content: "\f007"; +} + .command-panel .preview-list .operation { padding-top: 2px; padding-bottom: 2px; diff --git a/static/editor.css b/static/editor.css index 91aed0439..83ee6efb2 100644 --- a/static/editor.css +++ b/static/editor.css @@ -76,6 +76,10 @@ opacity: 1; } +.editor.is-blurred .line.cursor-line { + background: rgba(0, 0, 0, 0); +} + .editor .invisible { opacity: 0.2; } @@ -138,11 +142,15 @@ border-left: 1px solid; } -.editor:not(.is-focused) .cursor, +.editor .cursor, .editor.is-focused .cursor.blink-off { visibility: hidden; } +.editor.is-focused .cursor { + visibility: visible; +} + .editor .hidden-input { position: absolute; z-index: -1; diff --git a/static/fuzzy-finder.css b/static/fuzzy-finder.css index 76ef05462..f480ab0d3 100644 --- a/static/fuzzy-finder.css +++ b/static/fuzzy-finder.css @@ -30,4 +30,8 @@ .fuzzy-finder .file.pdf-name:before { content: "\f014"; -} \ No newline at end of file +} + +.fuzzy-finder .file.readme-name:before { + content: "\f007"; +} diff --git a/static/tree-view.css b/static/tree-view.css index 1568bfbc1..2bf94d527 100644 --- a/static/tree-view.css +++ b/static/tree-view.css @@ -112,6 +112,11 @@ top: -2px; } +.tree-view .file .readme-icon:before { + content: "\f007"; + top: -2px; +} + .tree-view .directory > .header .disclosure-arrow:before { content: "\f05a"; } diff --git a/themes/atom-dark-syntax.css b/themes/atom-dark-syntax.css index 92f39d650..5f19316c2 100644 --- a/themes/atom-dark-syntax.css +++ b/themes/atom-dark-syntax.css @@ -3,15 +3,15 @@ color: #c5c8c6; } -.editor.is-focused .cursor { +.editor .cursor { border-color: #FFFFFF; } -.editor.is-focused .selection .region { +.editor .selection .region { background-color: #333333; } -.editor.is-focused .line-number.cursor-line-no-selection, .editor.is-focused .line.cursor-line { +.editor .line-number.cursor-line-no-selection, .editor .line.cursor-line { background-color: rgba(255, 255, 255, 0.14); } diff --git a/themes/atom-dark-ui/blurred.css b/themes/atom-dark-ui/blurred.css new file mode 100644 index 000000000..cb9601b51 --- /dev/null +++ b/themes/atom-dark-ui/blurred.css @@ -0,0 +1,21 @@ +.is-blurred .tree-view { + background-color: #2a2a2a; +} + +.is-blurred .tabs { + opacity: 0.8; + border-bottom-color: #525252; +} + +.is-blurred .tab { + background-image: -webkit-linear-gradient(#444, #555); +} + +.is-blurred .tab.active { + border: 1px solid #525252 +} + +.is-blurred .tab.active:before { + box-shadow: 2px 2px 0 #525252; + border-color: #696969; +} \ No newline at end of file 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/markdown-preview.css b/themes/atom-dark-ui/markdown-preview.css index f5274f2df..5d58e2de7 100644 --- a/themes/atom-dark-ui/markdown-preview.css +++ b/themes/atom-dark-ui/markdown-preview.css @@ -29,6 +29,10 @@ font-family: Consolas, "Liberation Mono", Courier, monospace; } +.markdown-body a { + color: #4183c4; +} + .markdown-body ol > li { list-style-type: decimal; } diff --git a/themes/atom-dark-ui/package.json b/themes/atom-dark-ui/package.json index d0a007f7f..b223f8deb 100644 --- a/themes/atom-dark-ui/package.json +++ b/themes/atom-dark-ui/package.json @@ -8,6 +8,8 @@ "status-bar.css", "markdown-preview.css", "command-panel.css", - "command-logger.css" + "command-logger.css", + "blurred.css", + "bracket-matcher.css" ] } diff --git a/themes/atom-dark-ui/tabs.css b/themes/atom-dark-ui/tabs.css index 7a3d8b394..81b52334e 100644 --- a/themes/atom-dark-ui/tabs.css +++ b/themes/atom-dark-ui/tabs.css @@ -4,7 +4,7 @@ box-shadow: inset 0 -1px 0 #2e2e2e, 0 1px 0 #191919; } -.is-focused .tab { +.tab { background-image: -webkit-linear-gradient(#444, #3d3d3d); border-top: 1px solid #383838; border-right: 1px solid #2e2e2e; @@ -12,38 +12,16 @@ box-shadow: inset 0 0 5px #383838, 0 1px 0 #585858, inset -1px 0 0 #4a4a4a, inset 1px 0 0 #4a4a4a; } -.is-focused .tab:first-child { + .tab:first-child { box-shadow: inset 0 0 5px #383838, 0 1px 0 #585858, inset -1px 0 0 #4a4a4a; } -.is-focused .tab.active, -.is-focused .tab.active:hover { - border-top: 1px solid #4a4a4a; - box-shadow: inset -1px 0 0 #595959, inset 1px 0 0 #595959; - border-bottom-color: #424242; - background-image: -webkit-linear-gradient(#555555, #424242); -} - -.tab { - background-color: #555; - background-image: none; - border-top: 1px solid #383838; - border-right: 1px solid #2e2e2e; - border-bottom: 1px solid #2e2e2e; - box-shadow: inset 0 0 5px #555, 0 1px 0 #585858, inset -1px 0 0 #4a4a4a, inset 1px 0 0 #4a4a4a; -} - -.tab:first-child { - box-shadow: inset 0 0 5px #555, 0 1px 0 #585858, inset -1px 0 0 #4a4a4a; -} - .tab.active, .tab.active:hover { border-top: 1px solid #4a4a4a; box-shadow: inset -1px 0 0 #595959, inset 1px 0 0 #595959; border-bottom-color: #424242; - background-image: none; - background-color: #424242; + background-image: -webkit-linear-gradient(#555555, #424242); } .tab, @@ -55,9 +33,9 @@ border-color: #aaa; } -.is-focused .tab.active, -.is-focused .tab.active:hover, -.is-focused .tab.active .close-icon { +.tab.active, +.tab.active:hover, +.tab.active .close-icon { color: #e6e6e6; } @@ -74,8 +52,8 @@ border: 3px solid #777; } -.is-focused .tab.active:first-child, -.is-focused .tab.active:first-child:hover { +.tab.active:first-child, +.tab.active:first-child:hover { box-shadow: inset -1px 0 0 #595959; } @@ -84,18 +62,6 @@ border: 1px solid #595959; } -.is-focused .tab.active:before { - border-bottom-right-radius: 4px; - border-width: 0 1px 1px 0; - box-shadow: 2px 2px 0 #424242; -} - -.is-focused .tab.active:after { - border-bottom-left-radius: 4px; - border-width: 0 0 1px 1px; - box-shadow: -2px 2px 0 #424242; -} - .tab.active:before { border-bottom-right-radius: 4px; border-width: 0 1px 1px 0; @@ -104,6 +70,7 @@ .tab.active:after { border-bottom-left-radius: 4px; + border-width: 0 0 1px 1px; box-shadow: -2px 2px 0 #424242; } diff --git a/themes/atom-dark-ui/tree-view.css b/themes/atom-dark-ui/tree-view.css index 2b9383d63..a51e58c44 100644 --- a/themes/atom-dark-ui/tree-view.css +++ b/themes/atom-dark-ui/tree-view.css @@ -1,10 +1,5 @@ -.is-focused .tree-view { - background: #1e1e1e; - border-right: 1px solid #191919; -} - .tree-view { - background: #2e2e2e; + background: #1e1e1e; border-right: 1px solid #191919; } @@ -17,13 +12,8 @@ color: #d2d2d2; } -.is-focused .tree-view .selected > .highlight { - background-image: -webkit-linear-gradient(#4e4e4e, #434343); -} - .tree-view .selected > .highlight { - background-image: none; - background-color: #6e6e6e; + background-image: -webkit-linear-gradient(#4e4e4e, #434343); } .tree-view:focus .selected > .highlight { diff --git a/themes/atom-light-ui/blurred.css b/themes/atom-light-ui/blurred.css new file mode 100644 index 000000000..cc085ff29 --- /dev/null +++ b/themes/atom-light-ui/blurred.css @@ -0,0 +1,38 @@ +.is-blurred .tab { + background-image: none; + background-color: #e0e0e0); + border-top: none; + border-right: 1px solid #959595; + border-bottom: 1px solid #959595; + box-shadow: inset 0 0 5px #eee, 0 1px 0 #eee, inset -1px 0 0 #e0e0e0, inset 1px 0 0 #e0e0e0; + color: #323232; +} + +.is-blurred .tab.active { + border-bottom: 1px solid #e5e5e5; + box-shadow: inset 0 0 5px #eee, inset -1px 0 0 #e0e0e0, inset 1px 0 0 #e0e0e0; +} + +.is-blurred .tree-view { + background: #f3f3f3; + border-right: 1px solid #c5c5c5; +} + +.is-blurred .tree-view:focus .selected > .highlight { + border-top: 1px solid #3D4552; + border-bottom: 1px solid #3D4552; + background-image: none; + background-color: #69717b; +} + +.is-blurred .tree-view .selected > .highlight { + box-sizing: border-box; + border-top: 1px solid #97a4a7; + border-bottom: 1px solid #97a4a7; + background-image: none; + background-color: #DFDFDF; +} + +.is-blurred .tree-view .name:before { + color: #7e7e7e; +} 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/markdown-preview.css b/themes/atom-light-ui/markdown-preview.css index f5274f2df..5d58e2de7 100644 --- a/themes/atom-light-ui/markdown-preview.css +++ b/themes/atom-light-ui/markdown-preview.css @@ -29,6 +29,10 @@ font-family: Consolas, "Liberation Mono", Courier, monospace; } +.markdown-body a { + color: #4183c4; +} + .markdown-body ol > li { list-style-type: decimal; } diff --git a/themes/atom-light-ui/package.json b/themes/atom-light-ui/package.json index d0a007f7f..b223f8deb 100644 --- a/themes/atom-light-ui/package.json +++ b/themes/atom-light-ui/package.json @@ -8,6 +8,8 @@ "status-bar.css", "markdown-preview.css", "command-panel.css", - "command-logger.css" + "command-logger.css", + "blurred.css", + "bracket-matcher.css" ] } diff --git a/themes/atom-light-ui/tabs.css b/themes/atom-light-ui/tabs.css index 7bdba5938..6c37140a7 100644 --- a/themes/atom-light-ui/tabs.css +++ b/themes/atom-light-ui/tabs.css @@ -4,18 +4,8 @@ box-shadow: inset 0 -1px 0 #959595, 0 1px 0 #989898; } -.is-focused .tab { - background-image: -webkit-linear-gradient(#e0e0e0, #bfbfbf); - border-top: none; - border-right: 1px solid #959595; - border-bottom: 1px solid #959595; - box-shadow: inset 0 0 5px #eee, 0 1px 0 #eee, inset -1px 0 0 #e0e0e0, inset 1px 0 0 #e0e0e0; - color: #323232; -} - .tab { - background-image: none; - background-color: #e0e0e0); + background-image: -webkit-linear-gradient(#e0e0e0, #bfbfbf); border-top: none; border-right: 1px solid #959595; border-bottom: 1px solid #959595; diff --git a/themes/atom-light-ui/tree-view.css b/themes/atom-light-ui/tree-view.css index 638c58669..ff18a5990 100644 --- a/themes/atom-light-ui/tree-view.css +++ b/themes/atom-light-ui/tree-view.css @@ -1,13 +1,8 @@ -.is-focused .tree-view { +.tree-view { background: #dde3e8; border-right: 1px solid #989898; } -.tree-view { - background: #f3f3f3; - border-right: 1px solid #c5c5c5; -} - .tree-view .entry { text-shadow: 0 1px 0 #fff; } @@ -17,7 +12,7 @@ color: #262626; } -.is-focused .tree-view .selected > .highlight { +.tree-view .selected > .highlight { box-sizing: border-box; border-top: 1px solid #97a4a7; border-bottom: 1px solid #97a4a7; @@ -25,25 +20,10 @@ background-image: -webkit-linear-gradient(#cad5d8, #bcccce); } -.tree-view .selected > .highlight { - box-sizing: border-box; - border-top: 1px solid #97a4a7; - border-bottom: 1px solid #97a4a7; - background-image: none; - background-color: #DFDFDF; -} - -.is-focused .tree-view:focus .selected > .highlight { - border-top: 1px solid #3D4552; - border-bottom: 1px solid #3D4552; - background-image: -webkit-linear-gradient(#7e868d, #69717b); -} - .tree-view:focus .selected > .highlight { border-top: 1px solid #3D4552; border-bottom: 1px solid #3D4552; - background-image: none; - background-color: #69717b; + background-image: -webkit-linear-gradient(#7e868d, #69717b); } .tree-view:focus .directory.selected > .header > .name, @@ -62,13 +42,10 @@ color: #262626; } -.is-focused .tree-view .name:before { +.tree-view .name:before { color: #7e8692; } -.tree-view .name:before { - color: #7e7e7e; -} .tree-view .entry:hover, .tree-view .directory .header:hover .name,