diff --git a/.atom/atom.coffee b/.atom/user.coffee similarity index 100% rename from .atom/atom.coffee rename to .atom/user.coffee diff --git a/.atom/user.css b/.atom/user.css new file mode 100644 index 000000000..f53057581 --- /dev/null +++ b/.atom/user.css @@ -0,0 +1,8 @@ +// User styles +.tree-view { + +} + +.editor { + +} \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index caa40d8c9..6495d34a2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -43,3 +43,6 @@ [submodule "vendor/packages/property-list.tmbundle"] path = vendor/packages/property-list.tmbundle url = https://github.com/textmate/property-list.tmbundle.git +[submodule "vendor/packages/python.tmbundle"] + path = vendor/packages/python.tmbundle + url = https://github.com/textmate/python.tmbundle diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..ac09dc3e8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,20 @@ +# :rotating_light: Contributing to Atom :rotating_light: + +## Issues + * Include screenshots and animated GIFs whenever possible, they are immensely + helpful + * Include the behavior you expected to happen and other places you've seen + that behavior such as Emacs, vi, Xcode, etc. + * Check the Console app for stack traces to include if reporting a crash + * Check the Dev tools (`alt-cmd-i`) for errors and stack traces to include + +## Code + * Follow the [JavaScript](https://github.com/styleguide/javascript), + [CSS](https://github.com/styleguide/css), + and [Objective-C](https://github.com/github/objective-c-conventions) + styleguides + * Include thoughtfully worded [Jasmine](http://pivotal.github.com/jasmine/) + specs + * New packages go in `src/packages/` + * Add 3rd-party packages by submoduling in `vendor/packages/` + * Commit messages are in the present tense diff --git a/README.md b/README.md index f6e23611e..04459faa3 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Requirements Atom is installed! Type `atom [path]` from the commmand line or find it in `/Applications/Atom.app` ## Your ~/.atom Directory -A basic ~/.atom directory is installed when you run `rake install`. Take a look at ~/.atom/atom.coffee for more information. +A basic ~/.atom directory is installed when you run `rake install`. Take a look at ~/.atom/user.coffee for more information. ## Basic Keyboard shortcuts Atom doesn't have much in the way of menus yet. Use these keyboard shortcuts to @@ -53,7 +53,7 @@ Most default OS X keybindings also work. ## Init Script -Atom will require `~/.atom/atom.coffee` whenever a window is opened or reloaded if it is present in your +Atom will require `~/.atom/user.coffee` whenever a window is opened or reloaded if it is present in your home directory. This is a rudimentary jumping off point for your own customizations. ## Command Panel diff --git a/Rakefile b/Rakefile index e6adbba94..2a14471ac 100644 --- a/Rakefile +++ b/Rakefile @@ -76,12 +76,25 @@ task "create-dot-atom" do dot_atom_template_path = ATOM_SRC_PATH + "/.atom" replace_dot_atom = false - next if File.exists?(DOT_ATOM_PATH) + + if File.exists?(DOT_ATOM_PATH) + user_config = "#{DOT_ATOM_PATH}/user.coffee" + old_user_config = "#{DOT_ATOM_PATH}/atom.coffee" + + if File.exists?(old_user_config) + `mv #{old_user_config} #{user_config}` + puts "\033[32mRenamed #{old_user_config} to #{user_config}\033[0m" + end + + next + end `rm -rf "#{DOT_ATOM_PATH}"` `mkdir "#{DOT_ATOM_PATH}"` - `cp "#{dot_atom_template_path}/atom.coffee" "#{DOT_ATOM_PATH}"` - `cp "#{dot_atom_template_path}/packages" "#{DOT_ATOM_PATH}"` + + `cp "#{dot_atom_template_path}/user.coffee" "#{DOT_ATOM_PATH}"` + `cp "#{dot_atom_template_path}/user.css" "#{DOT_ATOM_PATH}"` + `cp -r "#{dot_atom_template_path}/packages" "#{DOT_ATOM_PATH}"` `cp -r "#{ATOM_SRC_PATH}/themes" "#{DOT_ATOM_PATH}"` `cp "#{ATOM_SRC_PATH}/vendor/themes/IR_Black.tmTheme" "#{DOT_ATOM_PATH}/themes"` end diff --git a/docs/configuring-and-extending.md b/docs/configuring-and-extending.md index 7ecd60a04..35ece3c59 100644 --- a/docs/configuring-and-extending.md +++ b/docs/configuring-and-extending.md @@ -55,8 +55,8 @@ Or you can use `observeConfig` to track changes from a view object. ```coffeescript class MyView extends View initialize: -> - @observeConfig 'editor.lineHeight', (lineHeight) => - @adjustLineHeight(lineHeight) + @observeConfig 'editor.fontSize', () => + @adjustFontSize() ``` The `observeConfig` method will call the given callback immediately with the diff --git a/docs/packages/intro.md b/docs/packages/intro.md index 4f37c9446..7e4d20bfd 100644 --- a/docs/packages/intro.md +++ b/docs/packages/intro.md @@ -18,9 +18,9 @@ my-package/ index.coffee ``` -**NOTE: NPM behavior is partially implemented until we get a working Node.js +**NOTE:** NPM behavior is partially implemented until we get a working Node.js API built into Atom. The goal is to make Atom packages be a superset of NPM -packages** +packages #### package.json diff --git a/native/atom_cef_client.cpp b/native/atom_cef_client.cpp index 38c25470b..3acb53951 100644 --- a/native/atom_cef_client.cpp +++ b/native/atom_cef_client.cpp @@ -141,6 +141,8 @@ bool AtomCefClient::OnKeyEvent(CefRefPtr browser, ToggleDevTools(browser); } else if (event.modifiers == EVENTFLAG_COMMAND_DOWN && event.unmodified_character == '`') { FocusNextWindow(); + } else if (event.modifiers == (EVENTFLAG_COMMAND_DOWN | EVENTFLAG_SHIFT_DOWN) && event.unmodified_character == '~') { + FocusPreviousWindow(); } else { return false; diff --git a/native/atom_cef_client.h b/native/atom_cef_client.h index 75b787138..d1961d8d3 100644 --- a/native/atom_cef_client.h +++ b/native/atom_cef_client.h @@ -105,6 +105,7 @@ class AtomCefClient : public CefClient, bool m_HandlePasteboardCommands = false; void FocusNextWindow(); + void FocusPreviousWindow(); void Open(std::string path); void Open(); void OpenUnstable(std::string path); diff --git a/native/atom_cef_client_mac.mm b/native/atom_cef_client_mac.mm index c20bd5fbe..be4879d97 100644 --- a/native/atom_cef_client_mac.mm +++ b/native/atom_cef_client_mac.mm @@ -23,6 +23,24 @@ void AtomCefClient::FocusNextWindow() { } } +void AtomCefClient::FocusPreviousWindow() { + NSArray *windows = [NSApp windows]; + int count = [windows count]; + int start = [windows indexOfObject:[NSApp keyWindow]]; + + int i = start; + while (true) { + i = i - 1; + if (i == 0) i = count -1; + if (i == start) break; + NSWindow *window = [windows objectAtIndex:i]; + if ([window isVisible] && ![window isExcludedFromWindowsMenu]) { + [window makeKeyAndOrderFront:nil]; + break; + } + } +} + void AtomCefClient::Open(std::string path) { NSString *pathString = [NSString stringWithCString:path.c_str() encoding:NSUTF8StringEncoding]; [(AtomApplication *)[AtomApplication sharedApplication] open:pathString]; diff --git a/script/fix-author b/script/fix-author new file mode 100755 index 000000000..bbbd3b3da --- /dev/null +++ b/script/fix-author @@ -0,0 +1,17 @@ +#!/bin/sh + +usage() { + echo "usage: $0 sha name email" + exit 1 +} + +if [ ! $3 ]; then + usage +fi + +git filter-branch -f --env-filter " +export GIT_AUTHOR_NAME='$2' +export GIT_AUTHOR_EMAIL='$3' +export GIT_COMMITTER_NAME='$2' +export GIT_COMMITTER_EMAIL='$3' +" -- $1..HEAD \ No newline at end of file diff --git a/spec/app/atom-spec.coffee b/spec/app/atom-spec.coffee index a02c84d97..73474a2a1 100644 --- a/spec/app/atom-spec.coffee +++ b/spec/app/atom-spec.coffee @@ -26,17 +26,20 @@ describe "the `atom` global", -> describe "keymap loading", -> describe "when package.json does not contain a 'keymaps' manifest", -> - it "loads all keymaps in the directory", -> + it "loads all the .cson/.json files in the keymaps directory", -> element1 = $$ -> @div class: 'test-1' element2 = $$ -> @div class: 'test-2' + element3 = $$ -> @div class: 'test-3' expect(keymap.bindingsForElement(element1)['ctrl-z']).toBeUndefined() expect(keymap.bindingsForElement(element2)['ctrl-z']).toBeUndefined() + expect(keymap.bindingsForElement(element3)['ctrl-z']).toBeUndefined() atom.loadPackage("package-with-module") expect(keymap.bindingsForElement(element1)['ctrl-z']).toBe "test-1" expect(keymap.bindingsForElement(element2)['ctrl-z']).toBe "test-2" + expect(keymap.bindingsForElement(element3)['ctrl-z']).toBeUndefined() describe "when package.json contains a 'keymaps' manifest", -> it "loads only the keymaps specified by the manifest, in the specified order", -> diff --git a/spec/app/buffer-spec.coffee b/spec/app/buffer-spec.coffee index ea10907cf..b13c2b7c2 100644 --- a/spec/app/buffer-spec.coffee +++ b/spec/app/buffer-spec.coffee @@ -465,6 +465,14 @@ describe 'Buffer', -> range = [[2,10], [4,10]] expect(buffer.getTextInRange(range)).toBe "ems.length <= 1) return items;\n var pivot = items.shift(), current, left = [], right = [];\n while(" + describe "when the range starts before the start of the buffer", -> + it "clips the range to the start of the buffer", -> + expect(buffer.getTextInRange([[-Infinity, -Infinity], [0, Infinity]])).toBe buffer.lineForRow(0) + + describe "when the range ends after the end of the buffer", -> + it "clips the range to the end of the buffer", -> + expect(buffer.getTextInRange([[12], [13, Infinity]])).toBe buffer.lineForRow(12) + describe ".scanInRange(range, regex, fn)", -> describe "when given a regex with a ignore case flag", -> it "does a case-insensitive search", -> diff --git a/spec/app/display-buffer-spec.coffee b/spec/app/display-buffer-spec.coffee index cd7ca484c..7444a3859 100644 --- a/spec/app/display-buffer-spec.coffee +++ b/spec/app/display-buffer-spec.coffee @@ -495,6 +495,20 @@ describe "DisplayBuffer", -> expect(displayBuffer.lineForRow(8).text).toMatch /^9-+/ expect(displayBuffer.lineForRow(10).fold).toBeDefined() + describe "when the line being deleted preceeds a fold", -> + describe "when the command is undone", -> + it "restores the line and preserves the fold", -> + editSession.setCursorBufferPosition([4]) + editSession.foldCurrentRow() + expect(editSession.isFoldedAtScreenRow(4)).toBeTruthy() + editSession.setCursorBufferPosition([3]) + editSession.deleteLine() + expect(editSession.isFoldedAtScreenRow(3)).toBeTruthy() + expect(buffer.lineForRow(3)).toBe ' while(items.length > 0) {' + editSession.undo() + expect(editSession.isFoldedAtScreenRow(4)).toBeTruthy() + expect(buffer.lineForRow(3)).toBe ' var pivot = items.shift(), current, left = [], right = [];' + describe ".clipScreenPosition(screenPosition, wrapBeyondNewlines: false, wrapAtSoftNewlines: false, skipAtomicTokens: false)", -> beforeEach -> displayBuffer.setSoftWrapColumn(50) diff --git a/spec/app/edit-session-spec.coffee b/spec/app/edit-session-spec.coffee index 6f72cd8bc..c77ddd15c 100644 --- a/spec/app/edit-session-spec.coffee +++ b/spec/app/edit-session-spec.coffee @@ -276,17 +276,22 @@ describe "EditSession", -> editSession.moveCursorToBeginningOfWord() expect(cursor1.getBufferPosition()).toEqual [0, 4] - expect(cursor2.getBufferPosition()).toEqual [1, 10] + expect(cursor2.getBufferPosition()).toEqual [1, 11] expect(cursor3.getBufferPosition()).toEqual [2, 39] it "does not fail at position [0, 0]", -> editSession.setCursorBufferPosition([0, 0]) editSession.moveCursorToBeginningOfWord() - it "works when the preceding line is blank", -> + it "treats lines with only whitespace as a word", -> + editSession.setCursorBufferPosition([11, 0]) + editSession.moveCursorToBeginningOfWord() + expect(editSession.getCursorBufferPosition()).toEqual [10, 0] + + it "works when the current line is blank", -> editSession.setCursorBufferPosition([10, 0]) editSession.moveCursorToBeginningOfWord() - expect(editSession.getCursorBufferPosition()).toEqual [9, 0] + expect(editSession.getCursorBufferPosition()).toEqual [9, 2] describe ".moveCursorToEndOfWord()", -> it "moves the cursor to the end of the word", -> @@ -298,8 +303,8 @@ describe "EditSession", -> editSession.moveCursorToEndOfWord() expect(cursor1.getBufferPosition()).toEqual [0, 13] - expect(cursor2.getBufferPosition()).toEqual [1, 13] - expect(cursor3.getBufferPosition()).toEqual [3, 4] + expect(cursor2.getBufferPosition()).toEqual [1, 12] + expect(cursor3.getBufferPosition()).toEqual [3, 7] it "does not blow up when there is no next word", -> editSession.setCursorBufferPosition [Infinity, Infinity] @@ -307,6 +312,17 @@ describe "EditSession", -> editSession.moveCursorToEndOfWord() expect(editSession.getCursorBufferPosition()).toEqual endPosition + it "treats lines with only whitespace as a word", -> + editSession.setCursorBufferPosition([9, 4]) + editSession.moveCursorToEndOfWord() + expect(editSession.getCursorBufferPosition()).toEqual [10, 0] + + it "works when the current line is blank", -> + editSession.setCursorBufferPosition([10, 0]) + editSession.moveCursorToEndOfWord() + expect(editSession.getCursorBufferPosition()).toEqual [11, 8] + + describe ".getCurrentParagraphBufferRange()", -> it "returns the buffer range of the current paragraph, delimited by blank lines or the beginning / end of the file", -> buffer.setText """ @@ -518,13 +534,13 @@ describe "EditSession", -> expect(editSession.getCursors().length).toBe 2 [cursor1, cursor2] = editSession.getCursors() expect(cursor1.getBufferPosition()).toEqual [0,4] - expect(cursor2.getBufferPosition()).toEqual [3,44] + expect(cursor2.getBufferPosition()).toEqual [3,47] expect(editSession.getSelections().length).toBe 2 [selection1, selection2] = editSession.getSelections() expect(selection1.getBufferRange()).toEqual [[0,4], [0,13]] expect(selection1.isReversed()).toBeTruthy() - expect(selection2.getBufferRange()).toEqual [[3,44], [3,49]] + expect(selection2.getBufferRange()).toEqual [[3,47], [3,49]] expect(selection2.isReversed()).toBeTruthy() describe ".selectToEndOfWord()", -> @@ -537,40 +553,49 @@ describe "EditSession", -> expect(editSession.getCursors().length).toBe 2 [cursor1, cursor2] = editSession.getCursors() expect(cursor1.getBufferPosition()).toEqual [0,13] - expect(cursor2.getBufferPosition()).toEqual [3,51] + expect(cursor2.getBufferPosition()).toEqual [3,50] expect(editSession.getSelections().length).toBe 2 [selection1, selection2] = editSession.getSelections() expect(selection1.getBufferRange()).toEqual [[0,4], [0,13]] expect(selection1.isReversed()).toBeFalsy() - expect(selection2.getBufferRange()).toEqual [[3,48], [3,51]] + expect(selection2.getBufferRange()).toEqual [[3,48], [3,50]] expect(selection2.isReversed()).toBeFalsy() describe ".selectWord()", -> - describe "when the cursor is inside a word", -> - it "selects the entire word", -> - editSession.setCursorScreenPosition([0, 8]) - editSession.selectWord() - expect(editSession.getSelectedText()).toBe 'quicksort' + describe "when the cursor is inside a word", -> + it "selects the entire word", -> + editSession.setCursorScreenPosition([0, 8]) + editSession.selectWord() + expect(editSession.getSelectedText()).toBe 'quicksort' - describe "when the cursor is between two words", -> - it "selects both words", -> - editSession.setCursorScreenPosition([0, 4]) - editSession.selectWord() - expect(editSession.getSelectedText()).toBe ' quicksort' + describe "when the cursor is between two words", -> + it "selects the word the cursor is on", -> + editSession.setCursorScreenPosition([0, 4]) + editSession.selectWord() + expect(editSession.getSelectedText()).toBe 'quicksort' - describe "when the cursor is inside a region of whitespace", -> - it "selects the whitespace region", -> - editSession.setCursorScreenPosition([5, 2]) - editSession.selectWord() - expect(editSession.getSelectedBufferRange()).toEqual [[5, 0], [5, 6]] + editSession.setCursorScreenPosition([0, 3]) + editSession.selectWord() + expect(editSession.getSelectedText()).toBe 'var' - describe "when the cursor is at the end of the text", -> - it "select the previous word", -> - editSession.buffer.append 'word' - editSession.moveCursorToBottom() - editSession.selectWord() - expect(editSession.getSelectedBufferRange()).toEqual [[12, 2], [12, 6]] + + describe "when the cursor is inside a region of whitespace", -> + it "selects the whitespace region", -> + editSession.setCursorScreenPosition([5, 2]) + editSession.selectWord() + expect(editSession.getSelectedBufferRange()).toEqual [[5, 0], [5, 6]] + + editSession.setCursorScreenPosition([5, 0]) + editSession.selectWord() + expect(editSession.getSelectedBufferRange()).toEqual [[5, 0], [5, 6]] + + describe "when the cursor is at the end of the text", -> + it "select the previous word", -> + editSession.buffer.append 'word' + editSession.moveCursorToBottom() + editSession.selectWord() + expect(editSession.getSelectedBufferRange()).toEqual [[12, 2], [12, 6]] describe ".setSelectedBufferRanges(ranges)", -> it "clears existing selections and creates selections for each of the given ranges", -> @@ -1110,25 +1135,26 @@ describe "EditSession", -> describe "when no text is selected", -> it "deletes all text between the cursor and the beginning of the word", -> editSession.setCursorBufferPosition([1, 24]) - editSession.addCursorAtBufferPosition([2, 5]) + editSession.addCursorAtBufferPosition([3, 5]) [cursor1, cursor2] = editSession.getCursors() editSession.backspaceToBeginningOfWord() expect(buffer.lineForRow(1)).toBe ' var sort = function(ems) {' - expect(buffer.lineForRow(2)).toBe ' f (items.length <= 1) return items;' + expect(buffer.lineForRow(3)).toBe ' ar pivot = items.shift(), current, left = [], right = [];' expect(cursor1.getBufferPosition()).toEqual [1, 22] - expect(cursor2.getBufferPosition()).toEqual [2, 4] + expect(cursor2.getBufferPosition()).toEqual [3, 4] editSession.backspaceToBeginningOfWord() expect(buffer.lineForRow(1)).toBe ' var sort = functionems) {' - expect(buffer.lineForRow(2)).toBe 'f (items.length <= 1) return items;' + expect(buffer.lineForRow(2)).toBe ' if (items.length <= 1) return itemsar pivot = items.shift(), current, left = [], right = [];' expect(cursor1.getBufferPosition()).toEqual [1, 21] - expect(cursor2.getBufferPosition()).toEqual [2, 0] + expect(cursor2.getBufferPosition()).toEqual [2, 39] editSession.backspaceToBeginningOfWord() - expect(buffer.lineForRow(1)).toBe ' var sort = emsf (items.length <= 1) return items;' + expect(buffer.lineForRow(1)).toBe ' var sort = ems) {' + expect(buffer.lineForRow(2)).toBe ' if (items.length <= 1) return ar pivot = items.shift(), current, left = [], right = [];' expect(cursor1.getBufferPosition()).toEqual [1, 13] - expect(cursor2.getBufferPosition()).toEqual [1, 16] + expect(cursor2.getBufferPosition()).toEqual [2, 34] describe "when text is selected", -> it "deletes only selected text", -> @@ -1296,7 +1322,7 @@ describe "EditSession", -> expect(cursor2.getBufferPosition()).toEqual [2, 5] editSession.deleteToEndOfWord() - expect(buffer.lineForRow(1)).toBe ' var sort = function(it' + expect(buffer.lineForRow(1)).toBe ' var sort = function(it {' expect(buffer.lineForRow(2)).toBe ' iitems.length <= 1) return items;' expect(cursor1.getBufferPosition()).toEqual [1, 24] expect(cursor2.getBufferPosition()).toEqual [2, 5] diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index 25dc557dc..dee1224d0 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -516,14 +516,61 @@ describe "Editor", -> editor.getBuffer().saveAs(path) expect(editor.getGrammar().name).toBe 'Plain Text' - describe "font size", -> - it "sets the initial font size based on the value from config", -> - config.set("editor.fontSize", 20) - newEditor = editor.splitRight() - expect(editor.css('font-size')).toBe '20px' - expect(newEditor.css('font-size')).toBe '20px' + describe "font family", -> + beforeEach -> + expect(editor.css('font-family')).not.toBe 'Courier' + + it "when there is no config in fontFamily don't set it", -> + expect($("head style.font-family")).not.toExist() + + describe "when the font family changes", -> + it "updates the font family on new and existing editors", -> + rootView.attachToDom() + rootView.height(200) + rootView.width(200) + + config.set("editor.fontFamily", "Courier") + newEditor = editor.splitRight() + + expect($("head style.font-family").text()).toMatch "{font-family: Courier}" + expect(editor.css('font-family')).toBe 'Courier' + expect(newEditor.css('font-family')).toBe 'Courier' + + it "updates the font family of editors and recalculates dimensions critical to cursor positioning", -> + rootView.attachToDom() + rootView.height(200) + rootView.width(200) + + lineHeightBefore = editor.lineHeight + charWidthBefore = editor.charWidth + config.set("editor.fontFamily", "Inconsolata") + editor.setCursorScreenPosition [5, 6] + expect(editor.charWidth).not.toBe charWidthBefore + expect(editor.getCursorView().position()).toEqual { top: 5 * editor.lineHeight, left: 6 * editor.charWidth } + expect(editor.verticalScrollbarContent.height()).toBe buffer.getLineCount() * editor.lineHeight + + describe "font size", -> + beforeEach -> + expect(editor.css('font-size')).not.toBe "20px" + expect(editor.css('font-size')).not.toBe "10px" + + it "sets the initial font size based on the value from config", -> + expect($("head style.font-size")).toExist() + expect($("head style.font-size").text()).toMatch "{font-size: #{config.get('editor.fontSize')}px}" + + describe "when the font size changes", -> + it "updates the font family on new and existing editors", -> + rootView.attachToDom() + rootView.height(200) + rootView.width(200) + + config.set("editor.fontSize", 20) + newEditor = editor.splitRight() + + expect($("head style.font-size").text()).toMatch "{font-size: 20px}" + expect(editor.css('font-size')).toBe '20px' + expect(newEditor.css('font-size')).toBe '20px' - describe "when the font size changes on the view", -> it "updates the font sizes of editors and recalculates dimensions critical to cursor positioning", -> rootView.attachToDom() rootView.height(200) @@ -568,6 +615,23 @@ describe "Editor", -> config.set("editor.fontSize", 10) expect(editor.renderedLines.find(".line").length).toBeGreaterThan originalLineCount + describe "when the editor is detached", -> + it "updates the font-size correctly and recalculates the dimensions by placing the rendered lines on the DOM", -> + rootView.attachToDom() + rootView.height(200) + rootView.width(200) + + newEditor = editor.splitRight() + newEditorParent = newEditor.parent() + newEditor.detach() + config.set("editor.fontSize", 10) + newEditorParent.append(newEditor) + + expect(newEditor.lineHeight).toBe editor.lineHeight + expect(newEditor.charWidth).toBe editor.charWidth + expect(newEditor.getCursorView().position()).toEqual editor.getCursorView().position() + expect(newEditor.verticalScrollbarContent.height()).toBe editor.verticalScrollbarContent.height() + describe "mouse events", -> beforeEach -> editor.attachToDom() @@ -1730,6 +1794,11 @@ describe "Editor", -> fold.destroy() expect(editor.gutter.find('.line-number').length).toBe 13 + it "styles folded line numbers", -> + editor.createFold(3, 5) + expect(editor.gutter.find('.line-number.fold').length).toBe 1 + expect(editor.gutter.find('.line-number.fold:eq(0)').text()).toBe '4' + describe "when the scrollView is scrolled to the right", -> it "adds a drop shadow to the gutter", -> editor.attachToDom() @@ -1898,16 +1967,18 @@ describe "Editor", -> editor.attachToDom() describe "when a fold-selection event is triggered", -> - it "folds the lines covered by the selection into a single line with a fold class", -> + it "folds the lines covered by the selection into a single line with a fold class and marker", -> editor.getSelection().setBufferRange(new Range([4, 29], [7, 4])) editor.trigger 'editor:fold-selection' expect(editor.renderedLines.find('.line:eq(4)')).toHaveClass('fold') + expect(editor.renderedLines.find('.line:eq(4) > .fold-marker')).toExist() expect(editor.renderedLines.find('.line:eq(5)').text()).toBe '8' expect(editor.getSelection().isEmpty()).toBeTruthy() expect(editor.getCursorScreenPosition()).toEqual [5, 0] + describe "when a fold placeholder line is clicked", -> it "removes the associated fold and places the cursor at its beginning", -> editor.setCursorBufferPosition([3,0]) @@ -1916,6 +1987,7 @@ describe "Editor", -> editor.find('.fold.line').mousedown() expect(editor.find('.fold')).not.toExist() + expect(editor.find('.fold-marker')).not.toExist() expect(editor.renderedLines.find('.line:eq(4)').text()).toMatch /4-+/ expect(editor.renderedLines.find('.line:eq(5)').text()).toMatch /5/ @@ -2260,3 +2332,196 @@ describe "Editor", -> it "copies the absolute path to the editor's file to the pasteboard", -> editor.trigger 'editor:copy-path' expect(pasteboard.read()[0]).toBe editor.getPath() + + describe "when editor:move-line-up is triggered", -> + describe "when there is no selection", -> + it "moves the line where the cursor is up", -> + editor.setCursorBufferPosition([1,0]) + editor.trigger 'editor:move-line-up' + expect(buffer.lineForRow(0)).toBe ' var sort = function(items) {' + expect(buffer.lineForRow(1)).toBe 'var quicksort = function () {' + + it "moves the cursor to the new row and the same column", -> + editor.setCursorBufferPosition([1,2]) + editor.trigger 'editor:move-line-up' + expect(editor.getCursorBufferPosition()).toEqual [0,2] + + describe "where there is a selection", -> + describe "when the selection falls inside the line", -> + it "maintains the selection", -> + editor.setSelectedBufferRange([[1, 2], [1, 5]]) + expect(editor.getSelectedText()).toBe 'var' + editor.trigger 'editor:move-line-up' + expect(editor.getSelectedBufferRange()).toEqual [[0, 2], [0, 5]] + expect(editor.getSelectedText()).toBe 'var' + + describe "where there are multiple lines selected", -> + it "moves the selected lines up", -> + editor.setSelectedBufferRange([[2, 0], [3, Infinity]]) + editor.trigger 'editor:move-line-up' + expect(buffer.lineForRow(0)).toBe 'var quicksort = function () {' + expect(buffer.lineForRow(1)).toBe ' if (items.length <= 1) return items;' + expect(buffer.lineForRow(2)).toBe ' var pivot = items.shift(), current, left = [], right = [];' + expect(buffer.lineForRow(3)).toBe ' var sort = function(items) {' + + it "maintains the selection", -> + editor.setSelectedBufferRange([[2, 0], [3, 62]]) + editor.trigger 'editor:move-line-up' + expect(editor.getSelectedBufferRange()).toEqual [[1, 0], [2, 62]] + + describe "when the last line is selected", -> + it "moves the selected line up", -> + editor.setSelectedBufferRange([[12, 0], [12, Infinity]]) + editor.trigger 'editor:move-line-up' + expect(buffer.lineForRow(11)).toBe '};' + expect(buffer.lineForRow(12)).toBe ' return sort(Array.apply(this, arguments));' + + describe "when the last two lines are selected", -> + it "moves the selected lines up", -> + editor.setSelectedBufferRange([[11, 0], [12, Infinity]]) + editor.trigger 'editor:move-line-up' + expect(buffer.lineForRow(10)).toBe ' return sort(Array.apply(this, arguments));' + expect(buffer.lineForRow(11)).toBe '};' + expect(buffer.lineForRow(12)).toBe '' + + describe "when the cursor is on the first line", -> + it "does not move the line", -> + editor.setCursorBufferPosition([0,0]) + originalText = editor.getText() + editor.trigger 'editor:move-line-up' + expect(editor.getText()).toBe originalText + + describe "when the cursor is on the trailing newline", -> + it "does not move the line", -> + editor.moveCursorToBottom() + editor.insertNewline() + editor.moveCursorToBottom() + originalText = editor.getText() + editor.trigger 'editor:move-line-up' + expect(editor.getText()).toBe originalText + + describe "when the cursor is on a folded line", -> + it "moves all lines in the fold up and preserves the fold", -> + editor.setCursorBufferPosition([4, 0]) + editor.foldCurrentRow() + editor.trigger 'editor:move-line-up' + expect(buffer.lineForRow(3)).toBe ' while(items.length > 0) {' + expect(buffer.lineForRow(7)).toBe ' var pivot = items.shift(), current, left = [], right = [];' + expect(editor.getSelectedBufferRange()).toEqual [[3, 0], [3, 0]] + expect(editor.isFoldedAtScreenRow(3)).toBeTruthy() + + describe "when the selection contains a folded and unfolded line", -> + it "moves the selected lines up and preserves the fold", -> + editor.setCursorBufferPosition([4, 0]) + editor.foldCurrentRow() + editor.setCursorBufferPosition([3, 4]) + editor.selectDown() + expect(editor.isFoldedAtScreenRow(4)).toBeTruthy() + editor.trigger 'editor:move-line-up' + expect(buffer.lineForRow(2)).toBe ' var pivot = items.shift(), current, left = [], right = [];' + expect(buffer.lineForRow(3)).toBe ' while(items.length > 0) {' + expect(editor.getSelectedBufferRange()).toEqual [[2, 4], [3, 0]] + expect(editor.isFoldedAtScreenRow(3)).toBeTruthy() + + describe "when an entire line is selected including the newline", -> + it "moves the selected line up", -> + editor.setCursorBufferPosition([1]) + editor.selectToEndOfLine() + editor.selectRight() + editor.trigger 'editor:move-line-up' + expect(buffer.lineForRow(0)).toBe ' var sort = function(items) {' + expect(buffer.lineForRow(1)).toBe 'var quicksort = function () {' + + describe "when editor:move-line-down is triggered", -> + describe "when there is no selection", -> + it "moves the line where the cursor is down", -> + editor.setCursorBufferPosition([0, 0]) + editor.trigger 'editor:move-line-down' + expect(buffer.lineForRow(0)).toBe ' var sort = function(items) {' + expect(buffer.lineForRow(1)).toBe 'var quicksort = function () {' + + it "moves the cursor to the new row and the same column", -> + editor.setCursorBufferPosition([0, 2]) + editor.trigger 'editor:move-line-down' + expect(editor.getCursorBufferPosition()).toEqual [1, 2] + + describe "when the cursor is on the last line", -> + it "does not move the line", -> + editor.moveCursorToBottom() + editor.trigger 'editor:move-line-down' + expect(buffer.lineForRow(12)).toBe '};' + expect(editor.getSelectedBufferRange()).toEqual [[12, 2], [12, 2]] + + describe "when the cursor is on the second to last line", -> + it "moves the line down", -> + editor.setCursorBufferPosition([11, 0]) + editor.trigger 'editor:move-line-down' + expect(buffer.lineForRow(11)).toBe '};' + expect(buffer.lineForRow(12)).toBe ' return sort(Array.apply(this, arguments));' + expect(buffer.lineForRow(13)).toBeUndefined() + + describe "when the cursor is on the second to last line and the last line is empty", -> + it "does not move the line", -> + editor.moveCursorToBottom() + editor.insertNewline() + editor.setCursorBufferPosition([12, 2]) + editor.trigger 'editor:move-line-down' + expect(buffer.lineForRow(12)).toBe '};' + expect(buffer.lineForRow(13)).toBe '' + expect(editor.getSelectedBufferRange()).toEqual [[12, 2], [12, 2]] + + describe "where there is a selection", -> + describe "when the selection falls inside the line", -> + it "maintains the selection", -> + editor.setSelectedBufferRange([[1, 2], [1, 5]]) + expect(editor.getSelectedText()).toBe 'var' + editor.trigger 'editor:move-line-down' + expect(editor.getSelectedBufferRange()).toEqual [[2, 2], [2, 5]] + expect(editor.getSelectedText()).toBe 'var' + + describe "where there are multiple lines selected", -> + it "moves the selected lines down", -> + editor.setSelectedBufferRange([[2, 0], [3, Infinity]]) + editor.trigger 'editor:move-line-down' + expect(buffer.lineForRow(2)).toBe ' while(items.length > 0) {' + expect(buffer.lineForRow(3)).toBe ' if (items.length <= 1) return items;' + expect(buffer.lineForRow(4)).toBe ' var pivot = items.shift(), current, left = [], right = [];' + expect(buffer.lineForRow(5)).toBe ' current = items.shift();' + + it "maintains the selection", -> + editor.setSelectedBufferRange([[2, 0], [3, 62]]) + editor.trigger 'editor:move-line-down' + expect(editor.getSelectedBufferRange()).toEqual [[3, 0], [4, 62]] + + describe "when the cursor is on a folded line", -> + it "moves all lines in the fold down and preserves the fold", -> + editor.setCursorBufferPosition([4, 0]) + editor.foldCurrentRow() + editor.trigger 'editor:move-line-down' + expect(buffer.lineForRow(4)).toBe ' return sort(left).concat(pivot).concat(sort(right));' + expect(buffer.lineForRow(5)).toBe ' while(items.length > 0) {' + expect(editor.getSelectedBufferRange()).toEqual [[5, 0], [5, 0]] + expect(editor.isFoldedAtScreenRow(5)).toBeTruthy() + + describe "when the selection contains a folded and unfolded line", -> + it "moves the selected lines down and preserves the fold", -> + editor.setCursorBufferPosition([4, 0]) + editor.foldCurrentRow() + editor.setCursorBufferPosition([3, 4]) + editor.selectDown() + expect(editor.isFoldedAtScreenRow(4)).toBeTruthy() + editor.trigger 'editor:move-line-down' + expect(buffer.lineForRow(3)).toBe ' return sort(left).concat(pivot).concat(sort(right));' + expect(buffer.lineForRow(4)).toBe ' var pivot = items.shift(), current, left = [], right = [];' + expect(buffer.lineForRow(5)).toBe ' while(items.length > 0) {' + expect(editor.getSelectedBufferRange()).toEqual [[4, 4], [5, 0]] + expect(editor.isFoldedAtScreenRow(5)).toBeTruthy() + + describe "when an entire line is selected including the newline", -> + it "moves the selected line down", -> + editor.setCursorBufferPosition([1]) + editor.selectToEndOfLine() + editor.selectRight() + 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) {' diff --git a/spec/app/point-spec.coffee b/spec/app/point-spec.coffee index 322bac5f2..d4d78715c 100644 --- a/spec/app/point-spec.coffee +++ b/spec/app/point-spec.coffee @@ -25,3 +25,9 @@ describe "Point", -> expect(new Point(5, 0).compare(new Point(6, 1))).toBe -1 expect(new Point(5, 5).compare(new Point(4, 1))).toBe 1 expect(new Point(5, 5).compare(new Point(5, 3))).toBe 1 + + describe ".translate(other)", -> + it "returns a translated point", -> + expect(new Point(1,2).translate([2,4])).toEqual [3,6] + expect(new Point(1,2).translate([-1])).toEqual [0,2] + expect(new Point(1,2).translate([0,-2])).toEqual [1,0] diff --git a/spec/app/range-spec.coffee b/spec/app/range-spec.coffee index 780962a5f..c73111ace 100644 --- a/spec/app/range-spec.coffee +++ b/spec/app/range-spec.coffee @@ -32,3 +32,8 @@ describe "Range", -> expect(new Range([2, 1], [3, 10]).union(new Range([1, 1], [2, 10]))).toEqual [[1, 1], [3, 10]] expect(new Range([2, 1], [3, 10]).union(new Range([2, 5], [3, 1]))).toEqual [[2, 1], [3, 10]] expect(new Range([2, 5], [3, 1]).union(new Range([2, 1], [3, 10]))).toEqual [[2, 1], [3, 10]] + + describe ".translate(startPoint, endPoint)", -> + it "returns a range translates by the specified start and end points", -> + expect(new Range([1, 1], [2, 10]).translate([1])).toEqual [[2, 1], [3, 10]] + expect(new Range([1, 1], [2, 10]).translate([1,2], [3,4])).toEqual [[2, 3], [5, 14]] diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index 20b5bca36..b046bef30 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -567,9 +567,9 @@ describe "RootView", -> editor = null beforeEach -> editor = rootView.getActiveEditor() + editor.attachToDom() it "increases/decreases font size when increase/decrease-font-size events are triggered", -> - editor = rootView.getActiveEditor() fontSizeBefore = editor.getFontSize() rootView.trigger 'window:increase-font-size' expect(editor.getFontSize()).toBe fontSizeBefore + 1 diff --git a/spec/app/syntax-spec.coffee b/spec/app/syntax-spec.coffee index d15eb5c74..9660fcecb 100644 --- a/spec/app/syntax-spec.coffee +++ b/spec/app/syntax-spec.coffee @@ -23,8 +23,7 @@ describe "the `syntax` global", -> expect(syntax.grammarForFilePath("/tmp/.git/config").name).toBe "Git Config" it "uses plain text if no grammar can be found", -> - filePath = require.resolve("this-is-not-a-real-file") - expect(syntax.grammarForFilePath(filePath).name).toBe "Plain Text" + expect(syntax.grammarForFilePath("this-is-not-a-real-file").name).toBe "Plain Text" describe ".getProperty(scopeDescriptor)", -> it "returns the property with the most specific scope selector", -> diff --git a/spec/fixtures/packages/package-with-module/keymaps/keymap-3.cjson b/spec/fixtures/packages/package-with-module/keymaps/keymap-3.cjson new file mode 100644 index 000000000..3c95c85a5 --- /dev/null +++ b/spec/fixtures/packages/package-with-module/keymaps/keymap-3.cjson @@ -0,0 +1,2 @@ +".test-3": + "ctrl-z": "test-3" diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index a0e49740a..1a4657488 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -85,7 +85,7 @@ jasmine.unspy = (object, methodName) -> throw new Error("Not a spy") unless object[methodName].originalValue? object[methodName] = object[methodName].originalValue -jasmine.getEnv().defaultTimeoutInterval = 500 +jasmine.getEnv().defaultTimeoutInterval = 1000 window.keyIdentifierForKey = (key) -> if key.length > 1 # named key diff --git a/spec/stdlib/fs-spec.coffee b/spec/stdlib/fs-spec.coffee index ffcdaf31b..b0a6fae5f 100644 --- a/spec/stdlib/fs-spec.coffee +++ b/spec/stdlib/fs-spec.coffee @@ -128,3 +128,8 @@ describe "fs", -> describe ".md5ForPath(path)", -> it "returns the MD5 hash of the file at the given path", -> expect(fs.md5ForPath(require.resolve('fixtures/sample.js'))).toBe 'dd38087d0d7e3e4802a6d3f9b9745f2b' + + describe ".list(path, extensions)", -> + it "returns the paths with the specified extensions", -> + path = require.resolve('fixtures/css.css') + expect(fs.list(require.resolve('fixtures'), ['.css'])).toEqual [path] diff --git a/src/app/atom-package.coffee b/src/app/atom-package.coffee index bb1ce071b..15a4e465a 100644 --- a/src/app/atom-package.coffee +++ b/src/app/atom-package.coffee @@ -26,15 +26,12 @@ class AtomPackage extends Package @metadata = fs.readObject(metadataPath) loadKeymaps: -> - for keymapPath in @getKeymapPaths() - keymap.load(keymapPath) - - getKeymapPaths: -> if keymaps = @metadata?.keymaps - keymaps.map (relativePath) => + keymaps = keymaps.map (relativePath) => fs.resolve(@keymapsDirPath, relativePath, ['cson', 'json', '']) + keymap.load(keymapPath) for keymapPath in keymaps else - fs.list(@keymapsDirPath) + keymap.loadDirectory(@keymapsDirPath) loadStylesheets: -> for stylesheetPath in @getStylesheetPaths() diff --git a/src/app/atom.coffee b/src/app/atom.coffee index 455382926..4630eb6fc 100644 --- a/src/app/atom.coffee +++ b/src/app/atom.coffee @@ -53,10 +53,16 @@ _.extend atom, themeNames = config.get("core.themes") ? ['Atom - Dark', 'IR_Black'] themeNames = [themeNames] unless _.isArray(themeNames) @loadTheme(themeName) for themeName in themeNames + @loadUserStylesheet() loadTheme: (name) -> @loadedThemes.push Theme.load(name) + loadUserStylesheet: -> + userStylesheetPath = fs.join(config.configDirPath, 'user.css') + if fs.isFile(userStylesheetPath) + applyStylesheet(userStylesheetPath, fs.read(userStylesheetPath), 'userTheme') + getAtomThemeStylesheets: -> themeNames = config.get("core.themes") ? ['Atom - Dark', 'IR_Black'] themeNames = [themeNames] unless _.isArray(themeNames) diff --git a/src/app/buffer.coffee b/src/app/buffer.coffee index 266b30ba2..db7de2e75 100644 --- a/src/app/buffer.coffee +++ b/src/app/buffer.coffee @@ -115,9 +115,9 @@ class Buffer new Range([0, 0], [@getLastRow(), @getLastLine().length]) getTextInRange: (range) -> - range = Range.fromObject(range) + range = @clipRange(range) if range.start.row == range.end.row - return @lines[range.start.row][range.start.column...range.end.column] + return @lineForRow(range.start.row)[range.start.column...range.end.column] multipleLines = [] multipleLines.push @lineForRow(range.start.row)[range.start.column..] # first line @@ -194,7 +194,7 @@ class Buffer startPoint = [start, 0] endPoint = [end + 1, 0] - @change(new Range(startPoint, endPoint), '') + @delete(new Range(startPoint, endPoint)) append: (text) -> @insert(@getEofPosition(), text) @@ -220,6 +220,10 @@ class Buffer new Point(row, column) + clipRange: (range) -> + range = Range.fromObject(range) + new Range(@clipPosition(range.start), @clipPosition(range.end)) + prefixAndSuffixForRange: (range) -> prefix: @lines[range.start.row][0...range.start.column] suffix: @lines[range.end.row][range.end.column..] diff --git a/src/app/config.coffee b/src/app/config.coffee index bf35eff63..26efbd674 100644 --- a/src/app/config.coffee +++ b/src/app/config.coffee @@ -4,7 +4,7 @@ EventEmitter = require 'event-emitter' configDirPath = fs.absolute("~/.atom") configJsonPath = fs.join(configDirPath, "config.json") -userInitScriptPath = fs.join(configDirPath, "atom.coffee") +userInitScriptPath = fs.join(configDirPath, "user.coffee") bundledPackagesDirPath = fs.join(resourcePath, "src/packages") bundledThemesDirPath = fs.join(resourcePath, "themes") vendoredPackagesDirPath = fs.join(resourcePath, "vendor/packages") diff --git a/src/app/cursor.coffee b/src/app/cursor.coffee index 4599705f3..8ac44739e 100644 --- a/src/app/cursor.coffee +++ b/src/app/cursor.coffee @@ -9,7 +9,6 @@ class Cursor screenPosition: null bufferPosition: null goalColumn: null - wordRegex: /(\w+)|([^\w\n]+)/g visible: true needsAutoscroll: false @@ -56,9 +55,18 @@ class Cursor isVisible: -> @visible + wordRegExp: -> + nonWordCharacters = config.get("editor.nonWordCharacters") + new RegExp("^[\t ]*$|[^\\s#{_.escapeRegExp(nonWordCharacters)}]+|[#{_.escapeRegExp(nonWordCharacters)}]+", "g") + isLastCursor: -> this == @editSession.getCursor() + isSurroundedByWhitespace: -> + {row, column} = @getBufferPosition() + range = [[row, Math.min(0, column - 1)], [row, Math.max(0, column + 1)]] + /^\s+$/.test @editSession.getTextInBufferRange(range) + autoscrolled: -> @needsAutoscroll = false @@ -147,14 +155,16 @@ class Cursor allowPrevious = options.allowPrevious ? true currentBufferPosition = @getBufferPosition() previousNonBlankRow = @editSession.buffer.previousNonBlankRow(currentBufferPosition.row) - previousLinesRange = [[previousNonBlankRow, 0], currentBufferPosition] + range = [[previousNonBlankRow, 0], currentBufferPosition] - beginningOfWordPosition = currentBufferPosition - @editSession.backwardsScanInRange (options.wordRegex || @wordRegex), previousLinesRange, (match, matchRange, { stop }) => + beginningOfWordPosition = null + @editSession.backwardsScanInRange (options.wordRegex ? @wordRegExp()), range, (match, matchRange, { stop }) => if matchRange.end.isGreaterThanOrEqual(currentBufferPosition) or allowPrevious beginningOfWordPosition = matchRange.start - stop() - beginningOfWordPosition + if not beginningOfWordPosition?.isEqual(currentBufferPosition) + stop() + + beginningOfWordPosition or currentBufferPosition getEndOfCurrentWordBufferPosition: (options = {}) -> allowNext = options.allowNext ? true @@ -162,11 +172,12 @@ class Cursor range = [currentBufferPosition, @editSession.getEofBufferPosition()] endOfWordPosition = null - @editSession.scanInRange (options.wordRegex || @wordRegex), range, (match, matchRange, { stop }) => - endOfWordPosition = matchRange.end - if not allowNext and matchRange.start.isGreaterThan(currentBufferPosition) - endOfWordPosition = currentBufferPosition - stop() + @editSession.scanInRange (options.wordRegex ? @wordRegExp()), range, (match, matchRange, { stop }) => + if matchRange.start.isLessThanOrEqual(currentBufferPosition) or allowNext + endOfWordPosition = matchRange.end + if not endOfWordPosition?.isEqual(currentBufferPosition) + stop() + endOfWordPosition or currentBufferPosition getCurrentWordBufferRange: (options={}) -> diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 74fe4a2a3..f6248c173 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -18,7 +18,7 @@ class EditSession if fs.exists(state.buffer) session = project.buildEditSessionForPath(state.buffer) else - console.warn "Could not build edit session for path '#{state.buffer}' because that file no longer exists" + console.warn "Could not build edit session for path '#{state.buffer}' because that file no longer exists" if state.buffer session = project.buildEditSessionForPath(null) session.setScrollTop(state.scrollTop) session.setScrollLeft(state.scrollLeft) @@ -307,6 +307,10 @@ class EditSession fold.destroy() @setCursorBufferPosition([fold.startRow, 0]) + isFoldedAtBufferRow: (bufferRow) -> + screenRow = @screenPositionForBufferPosition([bufferRow]).row + @isFoldedAtScreenRow(screenRow) + isFoldedAtScreenRow: (screenRow) -> @lineForScreenRow(screenRow)?.fold? @@ -334,6 +338,77 @@ class EditSession toggleLineCommentsForBufferRows: (start, end) -> @languageMode.toggleLineCommentsForBufferRows(start, end) + moveLineUp: -> + selection = @getSelectedBufferRange() + return if selection.start.row is 0 + lastRow = @buffer.getLastRow() + return if selection.isEmpty() and selection.start.row is lastRow and @buffer.getLastLine() is '' + + @transact => + foldedRows = [] + rows = [selection.start.row..selection.end.row] + if selection.start.row isnt selection.end.row and selection.end.column is 0 + rows.pop() unless @isFoldedAtBufferRow(selection.end.row) + for row in rows + screenRow = @screenPositionForBufferPosition([row]).row + if @isFoldedAtScreenRow(screenRow) + bufferRange = @bufferRangeForScreenRange([[screenRow], [screenRow + 1]]) + startRow = bufferRange.start.row + endRow = bufferRange.end.row - 1 + foldedRows.push(endRow - 1) + else + startRow = row + endRow = row + + endPosition = Point.min([endRow + 1], @buffer.getEofPosition()) + lines = @buffer.getTextInRange([[startRow], endPosition]) + if endPosition.row is lastRow and endPosition.column > 0 and not @buffer.lineEndingForRow(endPosition.row) + lines = "#{lines}\n" + @buffer.deleteRows(startRow, endRow) + @buffer.insert([startRow - 1], lines) + + @foldBufferRow(foldedRow) for foldedRow in foldedRows + + @setSelectedBufferRange(selection.translate([-1]), preserveFolds: true) + + moveLineDown: -> + selection = @getSelectedBufferRange() + lastRow = @buffer.getLastRow() + return if selection.end.row is lastRow + return if selection.end.row is lastRow - 1 and @buffer.getLastLine() is '' + + @transact => + foldedRows = [] + rows = [selection.end.row..selection.start.row] + if selection.start.row isnt selection.end.row and selection.end.column is 0 + rows.shift() unless @isFoldedAtBufferRow(selection.end.row) + for row in rows + screenRow = @screenPositionForBufferPosition([row]).row + if @isFoldedAtScreenRow(screenRow) + bufferRange = @bufferRangeForScreenRange([[screenRow], [screenRow + 1]]) + startRow = bufferRange.start.row + endRow = bufferRange.end.row - 1 + foldedRows.push(endRow + 1) + else + startRow = row + endRow = row + + if endRow + 1 is lastRow + endPosition = [endRow, @buffer.lineLengthForRow(endRow)] + else + endPosition = [endRow + 1] + lines = @buffer.getTextInRange([[startRow], endPosition]) + @buffer.deleteRows(startRow, endRow) + insertPosition = Point.min([startRow + 1], @buffer.getEofPosition()) + if insertPosition.row is @buffer.getLastRow() and insertPosition.column > 0 + lines = "\n#{lines}" + @buffer.insert(insertPosition, lines) + + @foldBufferRow(foldedRow) for foldedRow in foldedRows + + @setSelectedBufferRange(selection.translate([1]), preserveFolds: true) + + mutateSelectedText: (fn) -> @transact => fn(selection) for selection in @getSelections() diff --git a/src/app/editor.coffee b/src/app/editor.coffee index d4a1a4093..01b8ac40f 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -19,6 +19,7 @@ class Editor extends View autosave: false autoIndent: true autoIndentOnPaste: false + nonWordCharacters: "./\\()\"'-_:,.;<>~!@#$%^&*|+=[]{}`~?" @content: (params) -> @div class: @classes(params), tabindex: -1, => @@ -183,6 +184,8 @@ class Editor extends View 'editor:close-all-edit-sessions': @destroyAllEditSessions 'editor:select-grammar': @selectGrammar 'editor:copy-path': @copyPathToPasteboard + 'editor:move-line-up': @moveLineUp + 'editor:move-line-down': @moveLineDown documentation = {} for name, method of editorBindings @@ -204,6 +207,8 @@ class Editor extends View moveCursorToBeginningOfLine: -> @activeEditSession.moveCursorToBeginningOfLine() moveCursorToFirstCharacterOfLine: -> @activeEditSession.moveCursorToFirstCharacterOfLine() moveCursorToEndOfLine: -> @activeEditSession.moveCursorToEndOfLine() + moveLineUp: -> @activeEditSession.moveLineUp() + moveLineDown: -> @activeEditSession.moveLineDown() setCursorScreenPosition: (position) -> @activeEditSession.setCursorScreenPosition(position) getCursorScreenPosition: -> @activeEditSession.getCursorScreenPosition() getCursorScreenRow: -> @activeEditSession.getCursorScreenRow() @@ -271,6 +276,7 @@ class Editor extends View destroyFold: (foldId) -> @activeEditSession.destroyFold(foldId) destroyFoldsContainingBufferRow: (bufferRow) -> @activeEditSession.destroyFoldsContainingBufferRow(bufferRow) isFoldedAtScreenRow: (screenRow) -> @activeEditSession.isFoldedAtScreenRow(screenRow) + isFoldedAtBufferRow: (bufferRow) -> @activeEditSession.isFoldedAtBufferRow(bufferRow) lineForScreenRow: (screenRow) -> @activeEditSession.lineForScreenRow(screenRow) linesForScreenRows: (start, end) -> @activeEditSession.linesForScreenRows(start, end) @@ -312,7 +318,7 @@ class Editor extends View setInvisibles: (@invisibles={}) -> _.defaults @invisibles, eol: '\u00ac' - space: '\u2022' + space: '\u00b7' tab: '\u00bb' cr: '\u00a4' @resetDisplay() @@ -335,6 +341,7 @@ class Editor extends View @observeConfig 'editor.showInvisibles', (showInvisibles) => @setShowInvisibles(showInvisibles) @observeConfig 'editor.invisibles', (invisibles) => @setInvisibles(invisibles) @observeConfig 'editor.fontSize', (fontSize) => @setFontSize(fontSize) + @observeConfig 'editor.fontFamily', (fontFamily) => @setFontFamily(fontFamily) handleEvents: -> @on 'focus', => @@ -675,16 +682,38 @@ class Editor extends View autosave: -> @save() if @getPath()? - setFontSize: (@fontSize) -> - if fontSize? - @css('font-size', fontSize + 'px') - return unless @attached - @calculateDimensions() - @updatePaddingOfRenderedLines() - @updateLayerDimensions() - @requestDisplayUpdate() + setFontSize: (fontSize) -> + headTag = $("head") + styleTag = headTag.find("style.font-size") + if styleTag.length == 0 + styleTag = $$ -> @style class: 'font-size' + headTag.append styleTag - getFontSize: -> @fontSize + styleTag.text(".editor {font-size: #{fontSize}px}") + @redraw() + + getFontSize: -> + parseInt(@css("font-size")) + + setFontFamily: (fontFamily) -> + return if fontFamily == undefined + headTag = $("head") + styleTag = headTag.find("style.font-family") + if styleTag.length == 0 + styleTag = $$ -> @style class: 'font-family' + headTag.append styleTag + + styleTag.text(".editor {font-family: #{fontFamily}}") + @redraw() + + getFontFamily: -> @css("font-family") + + redraw: -> + return unless @attached + @calculateDimensions() + @updatePaddingOfRenderedLines() + @updateLayerDimensions() + @requestDisplayUpdate() newSplitEditor: (editSession) -> new Editor { editSession: editSession ? @activeEditSession.copy() } @@ -775,6 +804,10 @@ class Editor extends View @overlayer.append(view) calculateDimensions: -> + if not @isOnDom() + detachedEditorParent = _.last(@parents()) ? this + $(document.body).append(detachedEditorParent) + fragment = $('') line.join('') diff --git a/src/app/fold.coffee b/src/app/fold.coffee index 57bea3cfa..50e5d34ba 100644 --- a/src/app/fold.coffee +++ b/src/app/fold.coffee @@ -36,8 +36,8 @@ class Fold @displayBuffer.unregisterFold(@startRow, this) return - @updateStartRow(event) - @updateEndRow(event) + @startRow += @getRowDelta(event, @startRow) + @endRow += @getRowDelta(event, @endRow) if @startRow != oldStartRow @displayBuffer.unregisterFold(oldStartRow, this) @@ -49,26 +49,12 @@ class Fold isContainedByFold: (fold) -> @isContainedByRange(fold.getBufferRange()) - updateStartRow: (event) -> + getRowDelta: (event, row) -> { newRange, oldRange } = event - if oldRange.end.row < @startRow - delta = newRange.end.row - oldRange.end.row - else if newRange.end.row < @startRow - delta = newRange.end.row - @startRow + if oldRange.end.row <= row + newRange.end.row - oldRange.end.row + else if newRange.end.row < row + newRange.end.row - row else - delta = 0 - - @startRow += delta - - updateEndRow: (event) -> - { newRange, oldRange } = event - - if oldRange.end.row <= @endRow - delta = newRange.end.row - oldRange.end.row - else if newRange.end.row <= @endRow - delta = newRange.end.row - @endRow - else - delta = 0 - - @endRow += delta + 0 diff --git a/src/app/gutter.coffee b/src/app/gutter.coffee index e1e2f8a82..9f7de8f7a 100644 --- a/src/app/gutter.coffee +++ b/src/app/gutter.coffee @@ -58,16 +58,19 @@ class Gutter extends View @renderLineNumbers(renderFrom, renderTo) if performUpdate renderLineNumbers: (startScreenRow, endScreenRow) -> - rows = @editor().bufferRowsForScreenRows(startScreenRow, endScreenRow) + editor = @editor() + rows = editor.bufferRowsForScreenRows(startScreenRow, endScreenRow) - cursorScreenRow = @editor().getCursorScreenPosition().row + cursorScreenRow = editor.getCursorScreenPosition().row @lineNumbers[0].innerHTML = $$$ -> for row in rows if row == lastScreenRow rowValue = '•' else rowValue = row + 1 - @div {class: 'line-number'}, rowValue + classes = ['line-number'] + classes.push('fold') if editor.isFoldedAtBufferRow(row) + @div rowValue, class: classes.join(' ') lastScreenRow = row @calculateWidth() diff --git a/src/app/keymap.coffee b/src/app/keymap.coffee index 1918a429d..97cf4303b 100644 --- a/src/app/keymap.coffee +++ b/src/app/keymap.coffee @@ -35,7 +35,7 @@ class Keymap @loadDirectory(fs.join(config.configDirPath, 'keymaps')) loadDirectory: (directoryPath) -> - @load(filePath) for filePath in fs.list(directoryPath) + @load(filePath) for filePath in fs.list(directoryPath, ['.cson', '.json']) load: (path) -> @add(fs.readObject(path)) diff --git a/src/app/keymaps/editor.cson b/src/app/keymaps/editor.cson index 06f0dd571..4c977b698 100644 --- a/src/app/keymaps/editor.cson +++ b/src/app/keymaps/editor.cson @@ -38,3 +38,5 @@ 'meta-P': 'editor:close-all-edit-sessions' 'meta-L': 'editor:select-grammar' 'ctrl-C': 'editor:copy-path' + 'ctrl-meta-up': 'editor:move-line-up' + 'ctrl-meta-down': 'editor:move-line-down' diff --git a/src/app/line-map.coffee b/src/app/line-map.coffee index eae630887..1a5a74147 100644 --- a/src/app/line-map.coffee +++ b/src/app/line-map.coffee @@ -133,6 +133,7 @@ class LineMap new Range(start, end) bufferRangeForScreenRange: (screenRange) -> + screenRange = Range.fromObject(screenRange) start = @bufferPositionForScreenPosition(screenRange.start) end = @bufferPositionForScreenPosition(screenRange.end) new Range(start, end) @@ -141,4 +142,3 @@ class LineMap for row in [start..end] line = @lineForScreenRow(row).text console.log row, line, line.length - diff --git a/src/app/load-text-mate-packages-task.coffee b/src/app/load-text-mate-packages-task.coffee index 2eb904676..0bb0ee805 100644 --- a/src/app/load-text-mate-packages-task.coffee +++ b/src/app/load-text-mate-packages-task.coffee @@ -1,4 +1,4 @@ -Task = require 'Task' +Task = require 'task' module.exports = class LoadTextMatePackagesTask extends Task diff --git a/src/app/point.coffee b/src/app/point.coffee index a216cad0b..e471ad6c6 100644 --- a/src/app/point.coffee +++ b/src/app/point.coffee @@ -11,6 +11,14 @@ class Point new Point(row, column) + @min: (point1, point2) -> + point1 = @fromObject(point1) + point2 = @fromObject(point2) + if point1.isLessThanOrEqual(point2) + point1 + else + point2 + constructor: (@row=0, @column=0) -> copy: -> @@ -26,6 +34,10 @@ class Point new Point(row, column) + translate: (other) -> + other = Point.fromObject(other) + new Point(@row + other.row, @column + other.column) + splitAt: (column) -> if @row == 0 rightColumn = @column - column diff --git a/src/app/range.coffee b/src/app/range.coffee index 3464a3bce..136f52f15 100644 --- a/src/app/range.coffee +++ b/src/app/range.coffee @@ -48,6 +48,9 @@ class Range add: (point) -> new Range(@start.add(point), @end.add(point)) + translate: (startPoint, endPoint=startPoint) -> + new Range(@start.translate(startPoint), @end.translate(endPoint)) + intersectsWith: (otherRange) -> if @start.isLessThanOrEqual(otherRange.start) @end.isGreaterThanOrEqual(otherRange.start) diff --git a/src/app/selection.coffee b/src/app/selection.coffee index 8123d1157..abc702103 100644 --- a/src/app/selection.coffee +++ b/src/app/selection.coffee @@ -96,7 +96,10 @@ class Selection @screenRangeChanged() selectWord: -> - @setBufferRange(@cursor.getCurrentWordBufferRange()) + options = {} + options.wordRegex = /[\t ]*/ if @cursor.isSurroundedByWhitespace() + + @setBufferRange(@cursor.getCurrentWordBufferRange(options)) @wordwise = true @initialScreenRange = @getScreenRange() diff --git a/src/packages/command-logger/index.coffee b/src/packages/command-logger/index.coffee index 39dd9ad68..027035980 100644 --- a/src/packages/command-logger/index.coffee +++ b/src/packages/command-logger/index.coffee @@ -1,4 +1,5 @@ DeferredAtomPackage = require 'deferred-atom-package' +$ = require 'jquery' module.exports = class CommandLogger extends DeferredAtomPackage @@ -7,4 +8,25 @@ class CommandLogger extends DeferredAtomPackage instanceClass: 'command-logger/src/command-logger-view' - onLoadEvent: (event, instance) -> instance.toggle() + activate: (rootView, state={})-> + super + + @eventLog = state.eventLog ? {} + rootView.command 'command-logger:clear-data', => @eventLog = {} + + registerTriggeredEvent = (eventName) => + eventNameLog = @eventLog[eventName] + unless eventNameLog + eventNameLog = + count: 0 + name: eventName + @eventLog[eventName] = eventNameLog + eventNameLog.count++ + eventNameLog.lastRun = new Date().getTime() + originalTrigger = $.fn.trigger + $.fn.trigger = (eventName) -> + eventName = eventName.type if eventName.type + registerTriggeredEvent(eventName) if $(this).events()[eventName] + originalTrigger.apply(this, arguments) + + onLoadEvent: (event, instance) -> instance.toggle(@eventLog) diff --git a/src/packages/command-logger/spec/command-logger-spec.coffee b/src/packages/command-logger/spec/command-logger-spec.coffee index c01f24320..e2a06da2b 100644 --- a/src/packages/command-logger/spec/command-logger-spec.coffee +++ b/src/packages/command-logger/spec/command-logger-spec.coffee @@ -6,9 +6,8 @@ describe "CommandLogger", -> beforeEach -> rootView = new RootView(require.resolve('fixtures/sample.js')) - atom.loadPackage('command-logger').getInstance() + commandLogger = atom.loadPackage('command-logger') editor = rootView.getActiveEditor() - commandLogger = CommandLogger.instance afterEach -> rootView.deactivate() @@ -44,9 +43,11 @@ describe "CommandLogger", -> describe "when an event is ignored", -> it "does not create a node for that event", -> - commandLogger.ignoredEvents.push 'editor:delete-line' + commandLoggerView = commandLogger.getInstance() + commandLoggerView.ignoredEvents.push 'editor:delete-line' editor.trigger 'editor:delete-line' - nodes = commandLogger.createNodes() + commandLoggerView.eventLog = commandLogger.eventLog + nodes = commandLoggerView.createNodes() for node in nodes continue unless node.name is 'Editor' for child in node.children diff --git a/src/packages/command-logger/src/command-logger-view.coffee b/src/packages/command-logger/src/command-logger-view.coffee index 01496a2d0..ce8c8dbbb 100644 --- a/src/packages/command-logger/src/command-logger-view.coffee +++ b/src/packages/command-logger/src/command-logger-view.coffee @@ -1,12 +1,11 @@ {$$$} = require 'space-pen' ScrollView = require 'scroll-view' -$ = require 'jquery' _ = require 'underscore' module.exports = class CommandLoggerView extends ScrollView @activate: (rootView, state) -> - @instance = new CommandLoggerView(rootView, state?.eventLog) + @instance = new CommandLoggerView(rootView) @content: (rootView) -> @div class: 'command-logger', tabindex: -1, => @@ -31,29 +30,13 @@ class CommandLoggerView extends ScrollView 'tree-view:directory-modified' ] - initialize: (@rootView, @eventLog={}) -> + initialize: (@rootView) -> super - @rootView.command 'command-logger:clear-data', => @eventLog = {} @command 'core:cancel', => @detach() + @on 'blur', => @detach() unless document.activeElement is this[0] - registerEvent = (eventName) => - eventNameLog = @eventLog[eventName] - unless eventNameLog - eventNameLog = - count: 0 - name: eventName - @eventLog[eventName] = eventNameLog - eventNameLog.count++ - eventNameLog.lastRun = new Date().getTime() - - originalTrigger = $.fn.trigger - $.fn.trigger = (eventName) -> - eventName = eventName.type if eventName.type - registerEvent(eventName) if $(this).events()[eventName] - originalTrigger.apply(this, arguments) - - toggle: -> + toggle: (@eventLog={}) -> if @hasParent() @detach() else @@ -198,8 +181,11 @@ class CommandLoggerView extends ScrollView @focus() detach: -> - super() + return if @detaching + @detaching = true + super @rootView.focus() + @detaching = false serialize: -> eventLog: @eventLog diff --git a/src/packages/command-panel/spec/command-panel-spec.coffee b/src/packages/command-panel/spec/command-panel-spec.coffee index 635613912..a63ae410b 100644 --- a/src/packages/command-panel/spec/command-panel-spec.coffee +++ b/src/packages/command-panel/spec/command-panel-spec.coffee @@ -438,13 +438,13 @@ describe "CommandPanel", -> expect(previewList.getSelectedOperation()).toBe previewList.getOperations()[0] describe "when core:confirm is triggered on the preview list", -> - it "opens the operation's buffer, selects and scrolls to the search result, and focuses the active editor", -> + it "opens the operation's buffer, selects and scrolls to the search result, and refocuses the preview list", -> rootView.height(200) rootView.attachToDom() waitsForPromise -> commandPanel.execute('X x/apply/') # use apply because it is at the end of the file runs -> - spyOn(rootView, 'focus') + spyOn(previewList, 'focus') executeHandler = jasmine.createSpy('executeHandler') commandPanel.on 'core:confirm', executeHandler @@ -458,13 +458,13 @@ describe "CommandPanel", -> expect(editSession.getSelectedBufferRange()).toEqual operation.getBufferRange() expect(editSession.getSelectedBufferRange()).toEqual operation.getBufferRange() expect(editor.isScreenRowVisible(editor.getCursorScreenRow())).toBeTruthy() - expect(rootView.focus).toHaveBeenCalled() + expect(previewList.focus).toHaveBeenCalled() expect(executeHandler).not.toHaveBeenCalled() describe "when an operation in the preview list is clicked", -> - it "opens the operation's buffer, selects the search result, and focuses the active editor", -> - spyOn(rootView, 'focus') + it "opens the operation's buffer, selects the search result, and refocuses the preview list", -> + spyOn(previewList, 'focus') operation = previewList.getOperations()[4] previewList.find('li.operation:eq(4) span').mousedown() @@ -473,4 +473,4 @@ describe "CommandPanel", -> editSession = rootView.getActiveEditSession() expect(editSession.buffer.getPath()).toBe project.resolve(operation.getPath()) expect(editSession.getSelectedBufferRange()).toEqual operation.getBufferRange() - expect(rootView.focus).toHaveBeenCalled() + expect(previewList.focus).toHaveBeenCalled() diff --git a/src/packages/command-panel/src/command-panel-view.coffee b/src/packages/command-panel/src/command-panel-view.coffee index 7ccb74a48..d27711a70 100644 --- a/src/packages/command-panel/src/command-panel-view.coffee +++ b/src/packages/command-panel/src/command-panel-view.coffee @@ -51,7 +51,7 @@ class CommandPanelView extends View @previewList.hide() @previewCount.hide() @errorMessages.hide() - @prompt.iconSize(@miniEditor.fontSize) + @prompt.iconSize(@miniEditor.getFontSize()) serialize: -> text: @miniEditor.getText() diff --git a/src/packages/command-panel/src/preview-list.coffee b/src/packages/command-panel/src/preview-list.coffee index 34cbd240c..cb0296d46 100644 --- a/src/packages/command-panel/src/preview-list.coffee +++ b/src/packages/command-panel/src/preview-list.coffee @@ -83,7 +83,7 @@ class PreviewList extends ScrollView editSession = @rootView.open(operation.getPath()) bufferRange = operation.execute(editSession) editSession.setSelectedBufferRange(bufferRange, autoscroll: true) if bufferRange - @rootView.focus() + @focus() false getPathCount: -> diff --git a/src/packages/snippets/keymaps/snippets-1.cson b/src/packages/snippets/keymaps/snippets-1.cson index c8bf63c51..ce982b6a2 100644 --- a/src/packages/snippets/keymaps/snippets-1.cson +++ b/src/packages/snippets/keymaps/snippets-1.cson @@ -1,2 +1,2 @@ -window.keymap.bindKeys '.editor' +'.editor': 'tab': 'snippets:expand' diff --git a/src/packages/snippets/keymaps/snippets-2.cson b/src/packages/snippets/keymaps/snippets-2.cson index 6b660c6fd..9cf7b49e5 100644 --- a/src/packages/snippets/keymaps/snippets-2.cson +++ b/src/packages/snippets/keymaps/snippets-2.cson @@ -1,6 +1,6 @@ # it's critical that these bindings be loaded after those snippets-1 so they # are later in the cascade, hence breaking the keymap into 2 files -window.keymap.bindKeys '.editor' +'.editor': 'tab': 'snippets:next-tab-stop' 'shift-tab': 'snippets:previous-tab-stop' diff --git a/src/packages/snippets/spec/snippets-spec.coffee b/src/packages/snippets/spec/snippets-spec.coffee index 046807746..c9582a336 100644 --- a/src/packages/snippets/spec/snippets-spec.coffee +++ b/src/packages/snippets/spec/snippets-spec.coffee @@ -277,6 +277,7 @@ describe "Snippets extension", -> it "terminates the worker when loading completes", -> jasmine.unspy(LoadSnippetsTask.prototype, 'loadAtomSnippets') + spyOn(console, "warn") spyOn(Worker.prototype, 'terminate').andCallThrough() snippets.loaded = false snippets.loadAll() @@ -284,6 +285,8 @@ describe "Snippets extension", -> waitsFor "all snippets to load", 5000, -> snippets.loaded runs -> + expect(console.warn).toHaveBeenCalled() + expect(console.warn.argsForCall[0]).toMatch /Error reading snippets file '.*?\/spec\/fixtures\/packages\/package-with-snippets\/snippets\/junk-file'/ expect(Worker.prototype.terminate).toHaveBeenCalled() expect(Worker.prototype.terminate.calls.length).toBe 1 diff --git a/src/packages/snippets/src/load-snippets-task.coffee b/src/packages/snippets/src/load-snippets-task.coffee index bbef9d681..e32d70dde 100644 --- a/src/packages/snippets/src/load-snippets-task.coffee +++ b/src/packages/snippets/src/load-snippets-task.coffee @@ -1,4 +1,4 @@ -Task = require 'Task' +Task = require 'task' TextMatePackage = require 'text-mate-package' module.exports = diff --git a/src/packages/tabs/spec/tabs-spec.coffee b/src/packages/tabs/spec/tabs-spec.coffee index c3055d081..c36922a38 100644 --- a/src/packages/tabs/spec/tabs-spec.coffee +++ b/src/packages/tabs/spec/tabs-spec.coffee @@ -5,7 +5,7 @@ Tabs = require 'tabs' fs = require 'fs' describe "Tabs", -> - [rootView, editor, statusBar, buffer, tabs] = [] + [rootView, editor, buffer, tabs] = [] beforeEach -> rootView = new RootView(require.resolve('fixtures/sample.js')) @@ -118,3 +118,21 @@ describe "Tabs", -> tabs.find('.tab .close-icon:eq(1)').click() expect(editor.getActiveEditSessionIndex()).toBe 0 expect(editor.activeEditSession).toBe firstSession + + describe "when two tabs have the same file name", -> + [tempPath] = [] + + beforeEach -> + tempPath = '/tmp/sample.js' + fs.write(tempPath, 'sample') + + afterEach -> + fs.remove(tempPath) if fs.exists(tempPath) + + it "displays the parent folder name after the file name", -> + expect(tabs.find('.tab:eq(0) .file-name').text()).toBe 'sample.js' + rootView.open(tempPath) + expect(tabs.find('.tab:eq(0) .file-name').text()).toBe 'sample.js - fixtures' + expect(tabs.find('.tab:last .file-name').text()).toBe 'sample.js - tmp' + editor.destroyActiveEditSession() + expect(tabs.find('.tab:eq(0) .file-name').text()).toBe 'sample.js' diff --git a/src/packages/tabs/src/tab.coffee b/src/packages/tabs/src/tab.coffee index 6a26a114f..6a8ba4338 100644 --- a/src/packages/tabs/src/tab.coffee +++ b/src/packages/tabs/src/tab.coffee @@ -1,4 +1,5 @@ {View} = require 'space-pen' +fs = require 'fs' module.exports = class Tab extends View @@ -7,12 +8,14 @@ class Tab extends View @span class: 'file-name', outlet: 'fileName' @span class: 'close-icon' - initialize: (@editSession) -> + initialize: (@editSession, @editor) -> @buffer = @editSession.buffer @subscribe @buffer, 'path-changed', => @updateFileName() @subscribe @buffer, 'contents-modified', => @updateModifiedStatus() @subscribe @buffer, 'saved', => @updateModifiedStatus() @subscribe @buffer, 'git-status-changed', => @updateModifiedStatus() + @subscribe @editor, 'editor:edit-session-added', => @updateFileName() + @subscribe @editor, 'editor:edit-session-removed', => @updateFileName() @updateFileName() @updateModifiedStatus() @@ -25,4 +28,14 @@ class Tab extends View @isModified = false updateFileName: -> - @fileName.text(@editSession.buffer.getBaseName() ? 'untitled') + fileNameText = @editSession.buffer.getBaseName() + if fileNameText? + duplicates = @editor.getEditSessions().filter (session) -> fileNameText is session.buffer.getBaseName() + if duplicates.length > 1 + directory = fs.base(fs.directory(@editSession.getPath())) + fileNameText = "#{fileNameText} - #{directory}" if directory + else + fileNameText = 'untitled' + + @fileName.text(fileNameText) + @fileName.attr('title', @editSession.getPath()) diff --git a/src/packages/tabs/src/tabs-view.coffee b/src/packages/tabs/src/tabs-view.coffee index e7322dc64..d2b21fa7c 100644 --- a/src/packages/tabs/src/tabs-view.coffee +++ b/src/packages/tabs/src/tabs-view.coffee @@ -34,7 +34,7 @@ class Tabs extends View false addTabForEditSession: (editSession) -> - @append(new Tab(editSession)) + @append(new Tab(editSession, @editor)) setActiveTab: (index) -> @find(".tab.active").removeClass('active') diff --git a/src/packages/tree-view/spec/tree-view-spec.coffee b/src/packages/tree-view/spec/tree-view-spec.coffee index c788842bf..f0a83fe63 100644 --- a/src/packages/tree-view/spec/tree-view-spec.coffee +++ b/src/packages/tree-view/spec/tree-view-spec.coffee @@ -761,7 +761,7 @@ describe "TreeView", -> expect(addDialog.miniEditor.getText().length).toBe 0 - describe "tree-view:move", -> + fdescribe "tree-view:move", -> describe "when a file is selected", -> moveDialog = null @@ -770,6 +770,9 @@ describe "TreeView", -> treeView.trigger "tree-view:move" moveDialog = rootView.find(".tree-view-dialog").view() + afterEach -> + waits 50 # The move specs cause too many false positives because of their async nature, so wait a little bit before we cleanup + it "opens a move dialog with the file's current path (excluding extension) populated", -> extension = fs.extension(filePath) fileNameWithoutExtension = fs.base(filePath, extension) diff --git a/src/stdlib/fs.coffee b/src/stdlib/fs.coffee index a9eb755da..fcf8904a9 100644 --- a/src/stdlib/fs.coffee +++ b/src/stdlib/fs.coffee @@ -59,11 +59,16 @@ module.exports = # Returns an array with all the names of files contained # in the directory path. - list: (rootPath) -> + list: (rootPath, extensions) -> paths = [] - onPath = (path) => - paths.push(@join(rootPath, path)) - false + if extensions + onPath = (path) => + paths.push(@join(rootPath, path)) if _.contains(extensions, @extension(path)) + false + else + onPath = (path) => + paths.push(@join(rootPath, path)) + false @traverseTree(rootPath, onPath, onPath) paths diff --git a/src/stdlib/jquery-extensions.coffee b/src/stdlib/jquery-extensions.coffee index 365c2b5a8..089363577 100644 --- a/src/stdlib/jquery-extensions.coffee +++ b/src/stdlib/jquery-extensions.coffee @@ -25,6 +25,9 @@ $.fn.pageUp = -> $.fn.pageDown = -> @scrollTop(@scrollTop() + @height()) +$.fn.isOnDom = -> + @closest(document.body).length is 1 + $.fn.containsElement = (element) -> (element[0].compareDocumentPosition(this[0]) & 8) == 8 diff --git a/static/editor.css b/static/editor.css index bdd9f2825..40c3d1b4e 100644 --- a/static/editor.css +++ b/static/editor.css @@ -56,7 +56,6 @@ overflow-x: hidden; } - .editor .underlayer, .editor .lines, .editor .overlayer { width: 100%; height: 100%; @@ -103,4 +102,4 @@ position: absolute; pointer-events: none; z-index: -1; -} \ No newline at end of file +} diff --git a/themes/Atom - Dark/editor.css b/themes/Atom - Dark/editor.css index a9a8429d7..9dfb302ac 100644 --- a/themes/Atom - Dark/editor.css +++ b/themes/Atom - Dark/editor.css @@ -1,5 +1,6 @@ .editor { font-family: Inconsolata, Monaco, Courier; + line-height: 1.3; } .editor.mini { @@ -17,7 +18,7 @@ } .editor .gutter .line-number { - padding-right: 1.3em; + padding-right: .5em; min-width: 35px; box-sizing: border-box; text-align: right; @@ -43,12 +44,37 @@ -webkit-animation-iteration-count: 1; } -.editor .fold { - background-color: #444; +.editor .gutter .line-number.fold { + color: #fba0e3; + opacity: .8; } -.editor .fold.selected { - background-color: #244; +.editor .gutter .line-number.fold.cursor-line { + opacity: 1; +} + +.editor .gutter .line-number:after { + font-size: 0.8em; + content: '\f078'; + font-family: 'Octicons Regular'; + -webkit-font-smoothing: antialiased; + color: #fba0e3; + visibility: hidden; +} + +.editor .gutter .line-number.fold:after { + visibility: visible; +} + +.editor .fold-marker:after { + content: '\2026'; + opacity: .8; + color: #fba0e3; + padding-left: .2em; +} + +.editor .line.cursor-line .fold-marker { + opacity: 1; } .editor .invisible { diff --git a/themes/Atom - Light/editor.css b/themes/Atom - Light/editor.css index 9326721b9..3a3652b0b 100644 --- a/themes/Atom - Light/editor.css +++ b/themes/Atom - Light/editor.css @@ -1,5 +1,6 @@ .editor { font-family: Inconsolata, Monaco, Courier; + line-height: 1.3; } .editor.mini { @@ -20,7 +21,7 @@ } .editor .gutter .line-number { - padding-right: 1.3em; + padding-right: .5em; min-width: 35px; box-sizing: border-box; text-align: right; @@ -46,12 +47,37 @@ -webkit-animation-iteration-count: 1; } -.editor .fold { - background-color: #444; +.editor .gutter .line-number.fold { + color: #fba0e3; + opacity: .8; } -.editor .fold.selected { - background-color: #244; +.editor .gutter .line-number.fold.cursor-line { + opacity: 1; +} + +.editor .gutter .line-number:after { + font-size: 0.8em; + content: '\f078'; + font-family: 'Octicons Regular'; + -webkit-font-smoothing: antialiased; + color: #fba0e3; + visibility: hidden; +} + +.editor .gutter .line-number.fold:after { + visibility: visible; +} + +.editor .fold-marker:after { + content: '\2026'; + opacity: .8; + color: #fba0e3; + padding-left: .2em; +} + +.editor .line.cursor-line .fold-marker { + opacity: 1; } .editor .invisible { diff --git a/vendor/jasmine-console-reporter.js b/vendor/jasmine-console-reporter.js index fc6eb75d7..571c84aaa 100644 --- a/vendor/jasmine-console-reporter.js +++ b/vendor/jasmine-console-reporter.js @@ -31,7 +31,9 @@ jasmine.ConsoleReporter.prototype.reportSpecResults = function(spec) { for (var i = 0; i < resultItems.length; i++) { var result = resultItems[i]; if (this.logErrors && result.type == 'expect' && result.passed && !result.passed()) { - console.log("ERROR: " + spec.getFullName()) + message = spec.getFullName() + console.log("\n\n" + message) + console.log((new Array(message.length + 1)).join('-')) if (result.trace.stack) { console.log(result.trace.stack) } diff --git a/vendor/packages/python.tmbundle b/vendor/packages/python.tmbundle new file mode 160000 index 000000000..3675c22ae --- /dev/null +++ b/vendor/packages/python.tmbundle @@ -0,0 +1 @@ +Subproject commit 3675c22ae891419b27a80c58001831d01e73d431