diff --git a/keymaps/jumpy.cson b/keymaps/jumpy.cson index 874e00c..12d6069 100644 --- a/keymaps/jumpy.cson +++ b/keymaps/jumpy.cson @@ -7,10 +7,11 @@ # For more detailed documentation see # https://atom.io/docs/latest/advanced/keymaps -'atom-workspace atom-text-editor:not(.mini)': +'*:not(.jumpy-jump-mode) atom-workspace atom-text-editor:not(.mini), + *:not(.jumpy-jump-mode) atom-workspace': 'shift-enter': 'jumpy:toggle' -'atom-workspace atom-text-editor.jumpy-jump-mode': +'.jumpy-jump-mode atom-workspace': 'backspace': 'jumpy:reset' 'enter': 'jumpy:clear' 'space': 'jumpy:clear' diff --git a/lib/jumpy-view.coffee b/lib/jumpy-view.coffee index 71ab3cf..65ff619 100644 --- a/lib/jumpy-view.coffee +++ b/lib/jumpy-view.coffee @@ -3,48 +3,28 @@ # TODO: Remove space-pen? ### global atom ### -{CompositeDisposable, Point, Range} = require 'atom' +LabelManagerIterator = require './label-manager-iterator' +{CompositeDisposable} = require 'atom' {View, $} = require 'space-pen' _ = require 'lodash' -lowerCharacters = - (String.fromCharCode(a) for a in ['a'.charCodeAt()..'z'.charCodeAt()]) -upperCharacters = - (String.fromCharCode(a) for a in ['A'.charCodeAt()..'Z'.charCodeAt()]) -keys = [] - -# A little ugly. -# I used itertools.permutation in python. -# Couldn't find a good one in npm. Don't worry this takes < 1ms once. -for c1 in lowerCharacters - for c2 in lowerCharacters - keys.push c1 + c2 -for c1 in upperCharacters - for c2 in lowerCharacters - keys.push c1 + c2 -for c1 in lowerCharacters - for c2 in upperCharacters - keys.push c1 + c2 - class JumpyView extends View @content: -> @div '' initialize: () -> - @disposables = new CompositeDisposable() - @decorations = [] + @labelManager = new LabelManagerIterator @commands = new CompositeDisposable() - @commands.add atom.commands.add 'atom-workspace', 'jumpy:toggle': => @toggle() 'jumpy:reset': => @reset() - 'jumpy:clear': => @clearJumpMode() + 'jumpy:clear': @clearJumpMode - commands = {} - for characterSet in [lowerCharacters, upperCharacters] - for c in characterSet - do (c) => commands['jumpy:' + c] = => @getKey(c) + commands = LabelManagerIterator.chars.reduce( + (commands, c) => _.set(commands, "jumpy:#{c}", => @getKey c), + {} + ) @commands.add atom.commands.add 'atom-workspace', commands # TODO: consider moving this into toggle for new bindings. @@ -60,44 +40,23 @@ class JumpyView extends View getKey: (character) -> @statusBarJumpy?.classList.remove 'no-match' - isMatchOfCurrentLabels = (character, labelPosition) => - found = false - @disposables.add atom.workspace.observeTextEditors (editor) => - editorView = atom.views.getView(editor) - return if $(editorView).is ':not(:visible)' - - for decoration in @decorations - element = decoration.getProperties().item - if element.textContent[labelPosition] == character - found = true - return false - return found - # Assert: labelPosition will start at 0! labelPosition = (if not @firstChar then 0 else 1) - if !isMatchOfCurrentLabels character, labelPosition + unless @labelManager.isMatchOfCurrentLabels character, labelPosition @statusBarJumpy?.classList.add 'no-match' @statusBarJumpyStatus?.innerHTML = 'No match!' return - if not @firstChar + unless @firstChar @firstChar = character @statusBarJumpyStatus?.innerHTML = @firstChar - # TODO: Refactor this so not 2 calls to observeTextEditors - @disposables.add atom.workspace.observeTextEditors (editor) => - editorView = atom.views.getView(editor) - return if $(editorView).is ':not(:visible)' - - for decoration in @decorations - element = decoration.getProperties().item - if element.textContent.indexOf(@firstChar) != 0 - element.classList.add 'irrelevant' - else if not @secondChar + @labelManager.markIrrelevant @firstChar + else unless @secondChar @secondChar = character if @secondChar - @jump() # Jump first. Currently need the placement of the labels. - @clearJumpMode() + @jump() # Jump first. Currently need the placement of the labels. + _.defer @clearJumpMode clearKeys: -> @firstChar = null @@ -105,8 +64,7 @@ class JumpyView extends View reset: -> @clearKeys() - for decoration in @decorations - decoration.getProperties().item.classList.remove 'irrelevant' + @labelManager.unmarkIrrelevant() @statusBarJumpy?.classList.remove 'no-match' @statusBarJumpyStatus?.innerHTML = 'Jump Mode!' @@ -123,12 +81,8 @@ class JumpyView extends View # Set dirty for @clearJumpMode @cleared = false - # TODO: Can the following few lines be singleton'd up? ie. instance var? - wordsPattern = new RegExp (atom.config.get 'jumpy.matchPattern'), 'g' - fontSize = atom.config.get 'jumpy.fontSize' - fontSize = .75 if isNaN(fontSize) or fontSize > 1 - fontSize = (fontSize * 100) + '%' - highContrast = atom.config.get 'jumpy.highContrast' + # 'jumpy-jump-mode is for keymaps and utilized by tests + document.body.classList.add 'jumpy-jump-mode' @turnOffSlowKeys() @statusBarJumpy?.classList.remove 'no-match' @@ -137,154 +91,21 @@ class JumpyView extends View @statusBarJumpyStatus = document.querySelector '#status-bar-jumpy .status' - @allPositions = {} - nextKeys = _.clone keys - @disposables.add atom.workspace.observeTextEditors (editor) => - editorView = atom.views.getView(editor) - $editorView = $(editorView) - return if $editorView.is ':not(:visible)' - - # 'jumpy-jump-mode is for keymaps and utilized by tests - editorView.classList.add 'jumpy-jump-mode' - - getVisibleColumnRange = (editorView) -> - charWidth = editorView.getDefaultCharacterWidth() - # FYI: asserts: - # numberOfVisibleColumns = editorView.getWidth() / charWidth - minColumn = (editorView.getScrollLeft() / charWidth) - 1 - maxColumn = editorView.getScrollRight() / charWidth - - return [ - minColumn - maxColumn - ] - - drawLabels = (lineNumber, column) => - return unless nextKeys.length - - keyLabel = nextKeys.shift() - position = {row: lineNumber, column: column} - # creates a reference: - @allPositions[keyLabel] = - editor: editor.id - position: position - - marker = editor.markScreenRange new Range( - new Point(lineNumber, column), - new Point(lineNumber, column)), - invalidate: 'touch' - - labelElement = document.createElement('div') - labelElement.textContent = keyLabel - labelElement.style.fontSize = fontSize - labelElement.classList.add 'jumpy-label' - if highContrast - labelElement.classList.add 'high-contrast' - - decoration = editor.decorateMarker marker, - type: 'overlay' - item: labelElement - position: 'head' - @decorations.push decoration - - [minColumn, maxColumn] = getVisibleColumnRange editorView - rows = editor.getVisibleRowRange() - if rows - [firstVisibleRow, lastVisibleRow] = rows - # TODO: Right now there are issues with lastVisbleRow - for lineNumber in [firstVisibleRow...lastVisibleRow] - lineContents = editor.lineTextForScreenRow(lineNumber) - if editor.isFoldedAtScreenRow(lineNumber) - drawLabels lineNumber, 0 - else - while ((word = wordsPattern.exec(lineContents)) != null) - column = word.index - # Do not do anything... markers etc. - # if the columns are out of bounds... - if column > minColumn && column < maxColumn - drawLabels lineNumber, column - - @initializeClearEvents(editorView) - - clearJumpModeHandler: => - @clearJumpMode() - - initializeClearEvents: (editorView) -> - @disposables.add editorView.onDidChangeScrollTop => - @clearJumpModeHandler() - @disposables.add editorView.onDidChangeScrollLeft => - @clearJumpModeHandler() - - for e in ['blur', 'click'] - editorView.addEventListener e, @clearJumpModeHandler, true - - clearJumpMode: -> - clearAllMarkers = => - for decoration in @decorations - decoration.getMarker().destroy() - @decorations = [] # Very important for GC. - # Verifiable in Dev Tools -> Timeline -> Nodes. - - if @cleared - return + @labelManager.toggle() + @labelManager.initializeClearEvents @clearJumpMode + clearJumpMode: => + return if @cleared @cleared = true @clearKeys() @statusBarJumpy?.innerHTML = '' - @disposables.add atom.workspace.observeTextEditors (editor) => - editorView = atom.views.getView(editor) - - editorView.classList.remove 'jumpy-jump-mode' - for e in ['blur', 'click'] - editorView.removeEventListener e, @clearJumpModeHandler, true + document.body.classList.remove 'jumpy-jump-mode' atom.keymaps.keyBindings = @backedUpKeyBindings - clearAllMarkers() - @disposables?.dispose() + @labelManager.destroy() @detach() jump: -> - location = @findLocation() - if location == null - return - @disposables.add atom.workspace.observeTextEditors (currentEditor) => - editorView = atom.views.getView(currentEditor) - - # Prevent other editors from jumping cursors as well - # TODO: make a test for this return if - return if currentEditor.id != location.editor - - pane = atom.workspace.paneForItem(currentEditor) - pane.activate() - - isVisualMode = editorView.classList.contains 'visual-mode' - isSelected = (currentEditor.getSelections().length == 1 && - currentEditor.getSelectedText() != '') - if (isVisualMode || isSelected) - currentEditor.selectToScreenPosition location.position - else - currentEditor.setCursorScreenPosition location.position - - if atom.config.get 'jumpy.useHomingBeaconEffectOnJumps' - @drawBeacon currentEditor, location - - drawBeacon: (editor, location) -> - range = Range location.position, location.position - marker = editor.markScreenRange range, invalidate: 'never' - beacon = document.createElement 'span' - beacon.classList.add 'beacon' - editor.decorateMarker marker, - item: beacon, - type: 'overlay' - setTimeout -> - marker.destroy() - , 150 - - findLocation: -> - label = "#{@firstChar}#{@secondChar}" - if label of @allPositions - return @allPositions[label] - - return null + @labelManager.jumpTo @firstChar, @secondChar # Returns an object that can be retrieved when package is activated serialize: -> diff --git a/lib/label-manager-iterator.coffee b/lib/label-manager-iterator.coffee new file mode 100644 index 0000000..14f25c7 --- /dev/null +++ b/lib/label-manager-iterator.coffee @@ -0,0 +1,75 @@ +{Point, Range} = require 'atom' +fs = require 'fs' +pathHelper = require 'path' +_ = require 'lodash' + +LABEL_MANAGER_PATH = pathHelper.join __dirname, 'label-managers' +labelManagers = fs + .readdirSync(LABEL_MANAGER_PATH) + .map((file) -> require(pathHelper.join LABEL_MANAGER_PATH, file)) + +lowerCharacters = + (String.fromCharCode(a) for a in ['a'.charCodeAt()..'z'.charCodeAt()]) +upperCharacters = + (String.fromCharCode(a) for a in ['A'.charCodeAt()..'Z'.charCodeAt()]) +keys = [] + +# A little ugly. +# I used itertools.permutation in python. +# Couldn't find a good one in npm. Don't worry this takes < 1ms once. +for c1 in lowerCharacters + for c2 in lowerCharacters + keys.push c1 + c2 +for c1 in upperCharacters + for c2 in lowerCharacters + keys.push c1 + c2 +for c1 in lowerCharacters + for c2 in upperCharacters + keys.push c1 + c2 + +class LabelManagerIterator + @keys: keys + @chars: lowerCharacters.concat upperCharacters + + constructor: -> + @labelManagers = labelManagers.map((Manager) -> new Manager) + atom.config.observe 'jumpy.fontSize', @setFontSize + atom.config.observe 'jumpy.matchPattern', @setWordsPattern + atom.config.observe 'jumpy.highContrast', @setHighContrast + + setHighContrast: (value) => + manager.highContrast = value for manager in @labelManagers + + setWordsPattern: (value) => + value = new RegExp value, 'g' + manager.matchPattern = value for manager in @labelManagers + + setFontSize: (value) => + value = .75 if isNaN(value) or value > 1 + value = (value * 100) + '%' + manager.fontSize = value for manager in @labelManagers + + toggle: -> + nextKeys = _.clone keys + manager.toggle nextKeys for manager in @labelManagers + + jumpTo: (firstChar, secondChar) -> + manager.jumpTo firstChar, secondChar for manager in @labelManagers + + destroy: -> + manager.destroy() for manager in @labelManagers + + markIrrelevant: (firstChar) -> + manager.markIrrelevant firstChar for manager in @labelManagers + + unmarkIrrelevant: -> + manager.unmarkIrrelevant() for manager in @labelManagers + + isMatchOfCurrentLabels: (character, position) -> + @labelManagers.find (manager) -> + manager.isMatchOfCurrentLabels character, position + + initializeClearEvents: (clear) -> + manager.initializeClearEvents clear for manager in @labelManagers + +module.exports = LabelManagerIterator diff --git a/lib/label-manager.coffee b/lib/label-manager.coffee new file mode 100644 index 0000000..80a9811 --- /dev/null +++ b/lib/label-manager.coffee @@ -0,0 +1,41 @@ +{CompositeDisposable} = require 'atom' + +abstractMethod = (cls, methodName) -> + cls::[methodName] = -> + throw new Error( + "The abstract method #{cls.name}::#{methodName} needs to exist") + +class LabelManager + constructor: -> + @disposables = new CompositeDisposable + + createLabel: (text) -> + labelElement = document.createElement('span') + labelElement.textContent = text + labelElement.style.fontSize = @fontSize + labelElement.classList.add 'jumpy-label' + labelElement.classList.add 'high-contrast' if @highContrast + labelElement + + createBeacon: -> + beacon = document.createElement 'span' + beacon.classList.add 'beacon' + beacon + + destroy: -> + @disposables.dispose() + @disposables = new CompositeDisposable + + abstractMethod @, 'toggle' + + abstractMethod @, 'markIrrelevant' + + abstractMethod @, 'unmarkIrrelevant' + + abstractMethod @, 'isMatchOfCurrentLabels' + + abstractMethod @, 'jumpTo' + + abstractMethod @, 'initializeClearEvents' + +module.exports = LabelManager diff --git a/lib/label-managers/text-editor.coffee b/lib/label-managers/text-editor.coffee new file mode 100644 index 0000000..abf8184 --- /dev/null +++ b/lib/label-managers/text-editor.coffee @@ -0,0 +1,149 @@ +{Point, Range} = require 'atom' +{$} = require 'space-pen' +LabelManager = require '../label-manager' +{debounce} = require 'lodash' + +class TextEditorLabelManager extends LabelManager + constructor: -> + super + @allPositions = {} + @decorations = [] + + toggle: (keys) -> + @disposables.add atom.workspace.observeTextEditors (editor) => + editorView = atom.views.getView(editor) + $editorView = $(editorView) + return if $editorView.is ':not(:visible)' + + getVisibleColumnRange = (editorView) -> + charWidth = editorView.getDefaultCharacterWidth() + # FYI: asserts: + # numberOfVisibleColumns = editorView.getWidth() / charWidth + minColumn = (editorView.getScrollLeft() / charWidth) - 1 + maxColumn = editorView.getScrollRight() / charWidth + + return [ + minColumn + maxColumn + ] + + drawLabels = (lineNumber, column) => + return unless keys.length + + keyLabel = keys.shift() + position = {row: lineNumber, column: column} + # creates a reference: + @allPositions[keyLabel] = + editor: editor.id + position: position + + marker = editor.markScreenRange new Range( + new Point(lineNumber, column), + new Point(lineNumber, column)), + invalidate: 'touch' + + decoration = editor.decorateMarker marker, + type: 'overlay' + item: @createLabel keyLabel + position: 'head' + + @decorations.push decoration + + [minColumn, maxColumn] = getVisibleColumnRange editorView + rows = editor.getVisibleRowRange() + return unless rows + + [firstVisibleRow, lastVisibleRow] = rows + # TODO: Right now there are issues with lastVisbleRow + for lineNumber in [firstVisibleRow...lastVisibleRow] + lineContents = editor.lineTextForScreenRow(lineNumber) + if editor.isFoldedAtScreenRow(lineNumber) + drawLabels lineNumber, 0 + else + while ((word = @matchPattern.exec(lineContents)) != null) + column = word.index + # Do not do anything... markers etc. + # if the columns are out of bounds... + if column > minColumn && column < maxColumn + drawLabels lineNumber, column + + jumpTo: (firstChar, secondChar) -> + location = @findLocation firstChar, secondChar + if location == null + return + @disposables.add atom.workspace.observeTextEditors (currentEditor) => + editorView = atom.views.getView(currentEditor) + + # Prevent other editors from jumping cursors as well + # TODO: make a test for this return if + return if currentEditor.id != location.editor + + pane = atom.workspace.paneForItem(currentEditor) + pane.activate() + + isVisualMode = editorView.classList.contains 'visual-mode' + isSelected = (currentEditor.getSelections().length == 1 && + currentEditor.getSelectedText() != '') + if (isVisualMode || isSelected) + currentEditor.selectToScreenPosition location.position + else + currentEditor.setCursorScreenPosition location.position + + if atom.config.get 'jumpy.useHomingBeaconEffectOnJumps' + @drawBeacon currentEditor, location + + drawBeacon: (editor, location) -> + range = Range location.position, location.position + marker = editor.markScreenRange range, invalidate: 'never' + beacon = @createBeacon() + editor.decorateMarker marker, + item: beacon, + type: 'overlay' + setTimeout -> + marker.destroy() + , 150 + + destroy: -> + decoration.getMarker().destroy() for decoration in @decorations + @decorations = [] # Very important for GC. + # Verifiable in Dev Tools -> Timeline -> Nodes. + super + + findLocation: (firstChar, secondChar) -> + label = "#{firstChar}#{secondChar}" + @allPositions[label] || null + + markIrrelevant: (firstChar) -> + for decoration in @decorations + element = decoration.getProperties().item + if element.textContent.indexOf(firstChar) != 0 + element.classList.add 'irrelevant' + + unmarkIrrelevant: -> + for decoration in @decorations + decoration.getProperties().item.classList.remove 'irrelevant' + + findByCharacterAndPosition: (character, position) -> + for decoration in @decorations + element = decoration.getProperties().item + return decoration if element.textContent[position] == character + null + + isMatchOfCurrentLabels: (character, labelPosition) => + found = false + @disposables.add atom.workspace.observeTextEditors (editor) => + editorView = atom.views.getView(editor) + return if $(editorView).is ':not(:visible)' + found = @findByCharacterAndPosition character, labelPosition + return false if found + return !!found + + initializeClearEvents: (clear) -> + @disposables.add atom.workspace.observeTextEditors (editor) => + editorView = atom.views.getView(editor) + @disposables.add editorView.onDidChangeScrollTop clear + @disposables.add editorView.onDidChangeScrollLeft clear + for e in ['blur', 'click'] + editorView.addEventListener e, debounce(clear), true + +module.exports = TextEditorLabelManager diff --git a/lib/label-managers/tree-view.coffee b/lib/label-managers/tree-view.coffee new file mode 100644 index 0000000..40700fc --- /dev/null +++ b/lib/label-managers/tree-view.coffee @@ -0,0 +1,63 @@ +LabelManager = require '../label-manager' +{debounce} = require 'lodash' +{Disposable} = require 'atom' + +triggerMouseEvent = (element, eventType) -> + clickEvent = document.createEvent 'MouseEvents' + clickEvent.initEvent eventType, true, true + element.dispatchEvent clickEvent + +class TreeViewManager extends LabelManager + constructor: -> + super + @locations = [] + + toggle: (keys) -> + elements = document.querySelectorAll '.tree-view *[data-path]' + for element in elements when keys.length + label = @createLabel keys.shift() + @locations.push {label, element} + element.parentNode.insertBefore label, element + + destroy: -> + location.label.remove() while location = @locations.shift() + super + + drawBeacon: ({element}) -> + beacon = @createBeacon() + element.parentNode.insertBefore beacon, element + setTimeout beacon.remove.bind(beacon), 2000 + + jumpTo: (firstChar, secondChar) -> + match = "#{firstChar}#{secondChar}" + location = @locations.find ({label}) -> label.textContent is match + return unless location + @select location + @drawBeacon location + + select: ({element}) -> + atom.commands.dispatch element, 'tree-view:show' + triggerMouseEvent element, 'mousedown' + atom.commands.dispatch element, 'tree-view:open-selected-entry' + + markIrrelevant: (firstChar) -> + @locations + .filter(({label}) -> not label.textContent.startsWith firstChar) + .forEach(({label}) -> label.classList.add 'irrelevant') + + unmarkIrrelevant: -> + label.classList.remove 'irrelevant' for {label} in @locations + + isMatchOfCurrentLabels: (character, position) -> + @locations.find ({label}) -> label.textContent[position] is character + + initializeClearEvents: (clear) -> + clear = debounce clear + for treeView in document.getElementsByClassName('tree-view') + for e in ['blur', 'click'] + do (treeView, e) => + treeView.addEventListener e, clear + @disposables.add new Disposable -> + treeView.removeEventListener e, clear + +module.exports = TreeViewManager diff --git a/package.json b/package.json index af8c821..c55d165 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "extension" ], "activationCommands": { - "atom-text-editor": [ + "atom-workspace": [ "jumpy:toggle" ] }, @@ -28,7 +28,7 @@ "atom": ">0.50.0" }, "dependencies": { - "lodash": "^2.4.1", + "lodash": "^4.16.4", "space-pen": "^4.2.2" } } diff --git a/spec/jumpy-spec.coffee b/spec/jumpy-spec.coffee index 1e71837..539ccba 100644 --- a/spec/jumpy-spec.coffee +++ b/spec/jumpy-spec.coffee @@ -140,15 +140,15 @@ describe "Jumpy", -> describe "when the jumpy:toggle event is triggered and hotkeys are entered", -> - it "jumpy is cleared", (done) -> + beforeEach -> atom.commands.dispatch workspaceElement, 'jumpy:a' atom.commands.dispatch workspaceElement, 'jumpy:c' + advanceClock() + + it "jumpy is cleared", -> expect(textEditorElement.classList .contains('jumpy-jump-mode')).toBe false - setTimeout -> - expect(textEditor.getOverlayDecorations()).toHaveLength 0 - done() - , 160 + expect(textEditor.getOverlayDecorations()).toHaveLength 1 describe "when the jumpy:toggle event is triggered and invalid hotkeys are entered", -> @@ -169,7 +169,7 @@ describe "Jumpy", -> expect(cursorPosition.column).toBe 6 expect(textEditor.getSelectedText()).toBe '' it "clears jumpy mode", -> - expect(textEditorElement + expect(atom.document.body .classList.contains('jumpy-jump-mode')).toBeTruthy() atom.commands.dispatch workspaceElement, 'jumpy:a' atom.commands.dispatch workspaceElement, 'jumpy:c' @@ -367,40 +367,35 @@ describe "Jumpy", -> .toHaveLength expectedTotalNumberWith2TabsOpenInOnePane describe "when a jump mode is enabled", -> - activationPromise = [] beforeEach -> - activationPromise = atom.packages.activatePackage 'find-and-replace' - - it "clears when a find-and-replace mini pane is opened", -> - atom.commands.dispatch textEditorElement, 'find-and-replace:show' - waitsForPromise -> - activationPromise + promise = atom.packages.activatePackage 'find-and-replace' + atom.commands.dispatch(textEditorElement, + 'find-and-replace:show') + promise + runs advanceClock - runs -> - expect(textEditorElement - .classList.contains('jumpy-jump-mode')).toBe false - expect(textEditor.getOverlayDecorations()).toHaveLength 0 - expect(workspaceElement - .querySelectorAll('.find-and-replace')).toHaveLength 1 + it "clears when a find-and-replace mini pane is opened", -> + expect(workspaceElement + .querySelectorAll('.find-and-replace')).toHaveLength 1 + expect(textEditorElement + .classList.contains('jumpy-jump-mode')).toBe false + expect(textEditor.getOverlayDecorations()).toHaveLength 0 describe "when a jump mode is enabled", -> - activationPromise = [] beforeEach -> - activationPromise = atom.packages.activatePackage 'fuzzy-finder' + waitsForPromise -> + activationPromise = atom.packages.activatePackage 'fuzzy-finder' + atom.commands.dispatch textEditorElement, + 'fuzzy-finder:toggle-file-finder' + activationPromise + runs advanceClock it "clears when a fuzzy-finder mini pane is opened", -> atom.commands.dispatch textEditorElement, 'fuzzy-finder:toggle-file-finder' - - waitsForPromise -> - activationPromise - - runs -> - atom.commands.dispatch textEditorElement, - 'fuzzy-finder:toggle-file-finder' - expect(textEditorElement - .classList.contains('jumpy-jump-mode')).toBe false - expect(textEditor.getOverlayDecorations()).toHaveLength 0 - expect(workspaceElement - .querySelectorAll('.fuzzy-finder')).toHaveLength 1 + expect(textEditorElement + .classList.contains('jumpy-jump-mode')).toBe false + expect(textEditor.getOverlayDecorations()).toHaveLength 0 + expect(workspaceElement + .querySelectorAll('.fuzzy-finder')).toHaveLength 1 diff --git a/spec/jumpy-tree-view-spec.coffee b/spec/jumpy-tree-view-spec.coffee new file mode 100644 index 0000000..3689684 --- /dev/null +++ b/spec/jumpy-tree-view-spec.coffee @@ -0,0 +1,51 @@ +### global +atom jasmine describe xdescribe beforeEach it runs waitsForPromise +### + +path = require 'path' + +NUM_FILES = 3 +NUM_DIRS = 1 +DIR = path.join __dirname, 'fixtures' + +describe 'jumpy-tree-view', -> + + {workspaceElement} = {} + + beforeEach -> + atom.project.setPaths [DIR] + workspaceElement = atom.views.getView atom.workspace + workspaceElement.style.height = '5000px' + workspaceElement.style.width = '5000px' + jasmine.attachToDOM workspaceElement + waitsForPromise -> atom.packages.activatePackage 'tree-view' + runs -> atom.commands.dispatch 'tree-view:show' + waitsForPromise -> + promise = atom.packages.activatePackage 'jumpy' + atom.commands.dispatch workspaceElement, 'jumpy:toggle' + promise + + afterEach -> + atom.commands.dispatch workspaceElement, 'jumpy:clear' + + it 'adds labels to each element in the tree', -> + labels = atom.document.querySelectorAll '.tree-view .jumpy-label' + expect(labels.length).toBe NUM_DIRS + NUM_FILES + + it 'will open a file when selected with jumpy', -> + file = path.join DIR, 'test_text.md' + element = atom.document.querySelector "[data-path=\"#{file}\"]" + spyOn element, 'dispatchEvent' + atom.commands.dispatch workspaceElement, 'jumpy:a' + atom.commands.dispatch workspaceElement, 'jumpy:c' + expect(element.dispatchEvent).toHaveBeenCalled() + arg = element.dispatchEvent.mostRecentCall.args[0] + expect(arg instanceof MouseEvent).toBe yes + expect(arg.type).toEqual 'mousedown' + + it 'will open/close directories when selected with jumpy', -> + dir = atom.document.querySelector '.tree-view .directory' + expect(dir.classList.contains 'expanded').toBe true + atom.commands.dispatch workspaceElement, 'jumpy:a' + atom.commands.dispatch workspaceElement, 'jumpy:a' + expect(dir.classList.contains 'collapsed').toBe true diff --git a/styles/.atom-text-editor.less b/styles/.atom-text-editor.less index 227909d..cd72a9d 100644 --- a/styles/.atom-text-editor.less +++ b/styles/.atom-text-editor.less @@ -23,6 +23,11 @@ &.high-contrast { background-color: @background-color-success; } + + .tree-view & { + transform: translate(0, 0); + z-index: 11; + } } @import "beacon.less"; diff --git a/styles/beacon.less b/styles/beacon.less index 23c8f70..1c9bc30 100644 --- a/styles/beacon.less +++ b/styles/beacon.less @@ -13,7 +13,13 @@ -webkit-animation: rip .085s; content: ''; top: -6px; + + .tree-view & { + position: absolute; + z-index: 11; + } } + @-webkit-keyframes rip { 0% { box-shadow:0 0 0 0 transparent,