mirror of
https://github.com/atom/atom.git
synced 2026-04-06 03:02:13 -04:00
@@ -9,3 +9,4 @@ requireExtension 'status-bar'
|
||||
requireExtension 'wrap-guide'
|
||||
requireExtension 'markdown-preview'
|
||||
requireExtension 'outline-view'
|
||||
requireExtension 'tabs'
|
||||
@@ -152,7 +152,7 @@ describe "Editor", ->
|
||||
expect(otherEditSession.buffer.subscriptionCount()).toBe 0
|
||||
|
||||
describe "when 'close' is triggered", ->
|
||||
it "closes active edit session and loads next edit session", ->
|
||||
it "closes the active edit session and loads next edit session", ->
|
||||
editor.edit(rootView.project.buildEditSessionForPath())
|
||||
editSession = editor.activeEditSession
|
||||
spyOn(editSession.buffer, 'isModified').andReturn false
|
||||
@@ -163,6 +163,19 @@ describe "Editor", ->
|
||||
expect(editor.remove).not.toHaveBeenCalled()
|
||||
expect(editor.getBuffer()).toBe buffer
|
||||
|
||||
it "triggers the 'editor:edit-session-removed' event with the edit session and its former index", ->
|
||||
editor.edit(rootView.project.buildEditSessionForPath())
|
||||
editSession = editor.activeEditSession
|
||||
index = editor.getActiveEditSessionIndex()
|
||||
spyOn(editSession.buffer, 'isModified').andReturn false
|
||||
|
||||
editSessionRemovedHandler = jasmine.createSpy('editSessionRemovedHandler')
|
||||
editor.on 'editor:edit-session-removed', editSessionRemovedHandler
|
||||
editor.trigger "core:close"
|
||||
|
||||
expect(editSessionRemovedHandler).toHaveBeenCalled()
|
||||
expect(editSessionRemovedHandler.argsForCall[0][1..2]).toEqual [editSession, index]
|
||||
|
||||
it "calls remove on the editor if there is one edit session and mini is false", ->
|
||||
editSession = editor.activeEditSession
|
||||
expect(editor.mini).toBeFalsy()
|
||||
@@ -193,12 +206,18 @@ describe "Editor", ->
|
||||
otherEditSession = rootView.project.buildEditSessionForPath()
|
||||
|
||||
describe "when the edit session wasn't previously assigned to this editor", ->
|
||||
it "adds edit session to editor", ->
|
||||
it "adds edit session to editor and triggers the 'editor:edit-session-added' event", ->
|
||||
editSessionAddedHandler = jasmine.createSpy('editSessionAddedHandler')
|
||||
editor.on 'editor:edit-session-added', editSessionAddedHandler
|
||||
|
||||
originalEditSessionCount = editor.editSessions.length
|
||||
editor.edit(otherEditSession)
|
||||
expect(editor.activeEditSession).toBe otherEditSession
|
||||
expect(editor.editSessions.length).toBe originalEditSessionCount + 1
|
||||
|
||||
expect(editSessionAddedHandler).toHaveBeenCalled()
|
||||
expect(editSessionAddedHandler.argsForCall[0][1..2]).toEqual [otherEditSession, originalEditSessionCount]
|
||||
|
||||
describe "when the edit session was previously assigned to this editor", ->
|
||||
it "restores the previous edit session associated with the editor", ->
|
||||
previousEditSession = editor.activeEditSession
|
||||
@@ -277,6 +296,19 @@ describe "Editor", ->
|
||||
runs ->
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
|
||||
it "emits an editor:active-edit-session-changed event with the edit session and its index", ->
|
||||
activeEditSessionChangeHandler = jasmine.createSpy('activeEditSessionChangeHandler')
|
||||
editor.on 'editor:active-edit-session-changed', activeEditSessionChangeHandler
|
||||
|
||||
editor.setActiveEditSessionIndex(2)
|
||||
expect(activeEditSessionChangeHandler).toHaveBeenCalled()
|
||||
expect(activeEditSessionChangeHandler.argsForCall[0][1..2]).toEqual [editor.activeEditSession, 2]
|
||||
activeEditSessionChangeHandler.reset()
|
||||
|
||||
editor.setActiveEditSessionIndex(0)
|
||||
expect(activeEditSessionChangeHandler.argsForCall[0][1..2]).toEqual [editor.activeEditSession, 0]
|
||||
activeEditSessionChangeHandler.reset()
|
||||
|
||||
describe ".loadNextEditSession()", ->
|
||||
it "loads the next editor state and wraps to beginning when end is reached", ->
|
||||
expect(editor.activeEditSession).toBe session2
|
||||
|
||||
@@ -10,7 +10,7 @@ describe "TextMateGrammar", ->
|
||||
beforeEach ->
|
||||
grammar = TextMateBundle.grammarForFilePath("hello.coffee")
|
||||
|
||||
describe ".tokenizeLine(line, { ruleStack, tabLength })", ->
|
||||
describe ".tokenizeLine(line, ruleStack)", ->
|
||||
describe "when the entire line matches a single pattern with no capture groups", ->
|
||||
it "returns a single token with the correct scope", ->
|
||||
{tokens} = grammar.tokenizeLine("return")
|
||||
@@ -142,7 +142,7 @@ describe "TextMateGrammar", ->
|
||||
describe "when the pattern spans multiple lines", ->
|
||||
it "uses the ruleStack returned by the first line to parse the second line", ->
|
||||
{tokens: firstTokens, ruleStack} = grammar.tokenizeLine("'''single-quoted")
|
||||
{tokens: secondTokens, ruleStack} = grammar.tokenizeLine("heredoc'''", {ruleStack})
|
||||
{tokens: secondTokens, ruleStack} = grammar.tokenizeLine("heredoc'''", ruleStack)
|
||||
|
||||
expect(firstTokens.length).toBe 2
|
||||
expect(secondTokens.length).toBe 2
|
||||
|
||||
@@ -136,13 +136,17 @@ window.fakeClearTimeout = (idToClear) ->
|
||||
|
||||
window.advanceClock = (delta=1) ->
|
||||
window.now += delta
|
||||
callbacks = []
|
||||
|
||||
window.timeouts = window.timeouts.filter ([id, strikeTime, callback]) ->
|
||||
if strikeTime <= window.now
|
||||
callback()
|
||||
callbacks.push(callback)
|
||||
false
|
||||
else
|
||||
true
|
||||
|
||||
callback() for callback in callbacks
|
||||
|
||||
window.pagePixelPositionForPoint = (editor, point) ->
|
||||
point = Point.fromObject point
|
||||
top = editor.renderedLines.offset().top + point.row * editor.lineHeight
|
||||
|
||||
@@ -388,6 +388,7 @@ class Editor extends View
|
||||
if index == -1
|
||||
index = @editSessions.length
|
||||
@editSessions.push(editSession)
|
||||
@trigger 'editor:edit-session-added', [editSession, index]
|
||||
|
||||
@setActiveEditSessionIndex(index)
|
||||
|
||||
@@ -398,9 +399,11 @@ class Editor extends View
|
||||
@remove()
|
||||
else
|
||||
editSession = @activeEditSession
|
||||
index = @getActiveEditSessionIndex()
|
||||
@loadPreviousEditSession()
|
||||
_.remove(@editSessions, editSession)
|
||||
editSession.destroy()
|
||||
@trigger 'editor:edit-session-removed', [editSession, index]
|
||||
|
||||
loadNextEditSession: ->
|
||||
nextIndex = (@getActiveEditSessionIndex() + 1) % @editSessions.length
|
||||
@@ -430,6 +433,7 @@ class Editor extends View
|
||||
@trigger 'editor-path-change'
|
||||
|
||||
@trigger 'editor-path-change'
|
||||
@trigger 'editor:active-edit-session-changed', [@activeEditSession, index]
|
||||
@resetDisplay()
|
||||
|
||||
if @attached and @activeEditSession.buffer.isInConflict()
|
||||
|
||||
@@ -2,7 +2,8 @@ _ = require 'underscore'
|
||||
|
||||
module.exports =
|
||||
class ScreenLine
|
||||
constructor: ({@tokens, @ruleStack, @bufferRows, @startBufferColumn, @fold}) ->
|
||||
constructor: ({tokens, @ruleStack, @bufferRows, @startBufferColumn, @fold, tabLength}) ->
|
||||
@tokens = @breakOutAtomicTokens(tokens, tabLength)
|
||||
@bufferRows ?= 1
|
||||
@startBufferColumn ?= 0
|
||||
@text = _.pluck(@tokens, 'value').join('')
|
||||
@@ -90,3 +91,11 @@ class ScreenLine
|
||||
delta += token.bufferDelta
|
||||
return token if delta >= bufferColumn
|
||||
token
|
||||
|
||||
breakOutAtomicTokens: (inputTokens, tabLength) ->
|
||||
outputTokens = []
|
||||
breakOutLeadingWhitespace = true
|
||||
for token in inputTokens
|
||||
outputTokens.push(token.breakOutAtomicTokens(tabLength, breakOutLeadingWhitespace)...)
|
||||
breakOutLeadingWhitespace = token.isOnlyWhitespace() if breakOutLeadingWhitespace
|
||||
outputTokens
|
||||
|
||||
@@ -29,7 +29,7 @@ class TextMateGrammar
|
||||
data = {patterns: [data], tempName: name} if data.begin? or data.match?
|
||||
@repository[name] = new Rule(this, data)
|
||||
|
||||
tokenizeLine: (line, {ruleStack, tabLength}={}) ->
|
||||
tokenizeLine: (line, ruleStack=[@initialRule]) ->
|
||||
ruleStack ?= [@initialRule]
|
||||
ruleStack = new Array(ruleStack...) # clone ruleStack
|
||||
tokens = []
|
||||
@@ -62,15 +62,7 @@ class TextMateGrammar
|
||||
))
|
||||
break
|
||||
|
||||
{ tokens: @breakOutAtomicTokens(tokens, tabLength), ruleStack }
|
||||
|
||||
breakOutAtomicTokens: (inputTokens, tabLength) ->
|
||||
outputTokens = []
|
||||
breakOutLeadingWhitespace = true
|
||||
for token in inputTokens
|
||||
outputTokens.push(token.breakOutAtomicTokens(tabLength, breakOutLeadingWhitespace)...)
|
||||
breakOutLeadingWhitespace = token.isOnlyWhitespace() if breakOutLeadingWhitespace
|
||||
outputTokens
|
||||
{ tokens, ruleStack }
|
||||
|
||||
ruleForInclude: (name) ->
|
||||
if name[0] == "#"
|
||||
|
||||
@@ -66,7 +66,8 @@ class TokenizedBuffer
|
||||
|
||||
buildScreenLineForRow: (row, ruleStack) ->
|
||||
line = @buffer.lineForRow(row)
|
||||
new ScreenLine(@languageMode.tokenizeLine(line, {ruleStack, @tabLength}))
|
||||
{ tokens, ruleStack } = @languageMode.tokenizeLine(line, ruleStack)
|
||||
new ScreenLine({tokens, ruleStack, @tabLength})
|
||||
|
||||
lineForScreenRow: (row) ->
|
||||
@screenLines[row]
|
||||
|
||||
1
src/extensions/tabs/index.coffee
Normal file
1
src/extensions/tabs/index.coffee
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require 'tabs/src/tabs'
|
||||
91
src/extensions/tabs/spec/tabs-spec.coffee
Normal file
91
src/extensions/tabs/spec/tabs-spec.coffee
Normal file
@@ -0,0 +1,91 @@
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
RootView = require 'root-view'
|
||||
Tabs = require 'tabs'
|
||||
fs = require 'fs'
|
||||
|
||||
describe "Tabs", ->
|
||||
[rootView, editor, statusBar, buffer, tabs] = []
|
||||
|
||||
beforeEach ->
|
||||
rootView = new RootView(require.resolve('fixtures/sample.js'))
|
||||
rootView.open('sample.txt')
|
||||
rootView.simulateDomAttachment()
|
||||
rootView.activateExtension(Tabs)
|
||||
editor = rootView.getActiveEditor()
|
||||
tabs = rootView.find('.tabs').view()
|
||||
|
||||
afterEach ->
|
||||
rootView.remove()
|
||||
|
||||
describe "@activate", ->
|
||||
it "appends a status bear to all existing and new editors", ->
|
||||
expect(rootView.panes.find('.pane').length).toBe 1
|
||||
expect(rootView.panes.find('.pane > .tabs').length).toBe 1
|
||||
editor.splitRight()
|
||||
expect(rootView.find('.pane').length).toBe 2
|
||||
expect(rootView.panes.find('.pane > .tabs').length).toBe 2
|
||||
|
||||
describe "#initialize()", ->
|
||||
it "creates a tab for each edit session on the editor to which the tab-strip belongs", ->
|
||||
expect(editor.editSessions.length).toBe 2
|
||||
expect(tabs.find('.tab').length).toBe 2
|
||||
|
||||
expect(tabs.find('.tab:eq(0) .file-name').text()).toBe editor.editSessions[0].buffer.getBaseName()
|
||||
expect(tabs.find('.tab:eq(1) .file-name').text()).toBe editor.editSessions[1].buffer.getBaseName()
|
||||
|
||||
it "highlights the tab for the current active edit session", ->
|
||||
expect(editor.getActiveEditSessionIndex()).toBe 1
|
||||
expect(tabs.find('.tab:eq(1)')).toHaveClass 'active'
|
||||
|
||||
describe "when the active edit session changes", ->
|
||||
it "highlights the tab for the newly-active edit session", ->
|
||||
editor.setActiveEditSessionIndex(0)
|
||||
expect(tabs.find('.active').length).toBe 1
|
||||
expect(tabs.find('.tab:eq(0)')).toHaveClass 'active'
|
||||
|
||||
editor.setActiveEditSessionIndex(1)
|
||||
expect(tabs.find('.active').length).toBe 1
|
||||
expect(tabs.find('.tab:eq(1)')).toHaveClass 'active'
|
||||
|
||||
describe "when a new edit session is created", ->
|
||||
it "adds a tab for the new edit session", ->
|
||||
rootView.open('two-hundred.txt')
|
||||
expect(tabs.find('.tab').length).toBe 3
|
||||
expect(tabs.find('.tab:eq(2) .file-name').text()).toBe 'two-hundred.txt'
|
||||
|
||||
describe "when the edit session's buffer has an undefined path", ->
|
||||
it "makes the tab text 'untitled'", ->
|
||||
rootView.open()
|
||||
expect(tabs.find('.tab').length).toBe 3
|
||||
expect(tabs.find('.tab:eq(2) .file-name').text()).toBe 'untitled'
|
||||
|
||||
describe "when an edit session is removed", ->
|
||||
it "removes the tab for the removed edit session", ->
|
||||
editor.setActiveEditSessionIndex(0)
|
||||
editor.destroyActiveEditSession()
|
||||
expect(tabs.find('.tab').length).toBe 1
|
||||
expect(tabs.find('.tab:eq(0) .file-name').text()).toBe 'sample.txt'
|
||||
|
||||
describe "when a tab is clicked", ->
|
||||
it "activates the associated edit session", ->
|
||||
expect(editor.getActiveEditSessionIndex()).toBe 1
|
||||
tabs.find('.tab:eq(0)').click()
|
||||
expect(editor.getActiveEditSessionIndex()).toBe 0
|
||||
tabs.find('.tab:eq(1)').click()
|
||||
expect(editor.getActiveEditSessionIndex()).toBe 1
|
||||
|
||||
describe "when a file name associated with a tab changes", ->
|
||||
[buffer, newPath] = []
|
||||
|
||||
beforeEach ->
|
||||
buffer = editor.editSessions[0].buffer
|
||||
oldPath = buffer.getPath()
|
||||
newPath = oldPath.replace(/sample.js$/, "foobar.js")
|
||||
|
||||
afterEach ->
|
||||
fs.remove(newPath)
|
||||
|
||||
it "updates the file name in the tab", ->
|
||||
buffer.saveAs(newPath)
|
||||
expect(tabs.find('.tab:first .file-name')).toHaveText "foobar.js"
|
||||
14
src/extensions/tabs/src/tab.coffee
Normal file
14
src/extensions/tabs/src/tab.coffee
Normal file
@@ -0,0 +1,14 @@
|
||||
{View} = require 'space-pen'
|
||||
|
||||
module.exports =
|
||||
class Tab extends View
|
||||
@content: (editSession) ->
|
||||
@div class: 'tab', =>
|
||||
@div class: 'file-name', outlet: 'fileName'
|
||||
|
||||
initialize: (@editSession) ->
|
||||
@updateFileName()
|
||||
@editSession.on 'buffer-path-change.tab', => @updateFileName()
|
||||
|
||||
updateFileName: ->
|
||||
@fileName.text(@editSession.buffer.getBaseName() ? 'untitled')
|
||||
43
src/extensions/tabs/src/tabs.coffee
Normal file
43
src/extensions/tabs/src/tabs.coffee
Normal file
@@ -0,0 +1,43 @@
|
||||
$ = require 'jquery'
|
||||
{View} = require 'space-pen'
|
||||
Tab = require 'tabs/src/tab'
|
||||
|
||||
module.exports =
|
||||
class Tabs extends View
|
||||
@activate: (rootView) ->
|
||||
requireStylesheet 'tabs/src/tabs.css'
|
||||
|
||||
for editor in rootView.getEditors()
|
||||
@prependToEditorPane(rootView, editor) if rootView.parents('html').length
|
||||
|
||||
rootView.on 'editor-open', (e, editor) =>
|
||||
@prependToEditorPane(rootView, editor)
|
||||
|
||||
@prependToEditorPane: (rootView, editor) ->
|
||||
if pane = editor.pane()
|
||||
pane.prepend(new Tabs(editor))
|
||||
|
||||
@content: ->
|
||||
@div class: 'tabs'
|
||||
|
||||
initialize: (@editor) ->
|
||||
for editSession, index in @editor.editSessions
|
||||
@addTabForEditSession(editSession)
|
||||
|
||||
@setActiveTab(@editor.getActiveEditSessionIndex())
|
||||
@editor.on 'editor:active-edit-session-changed', (e, editSession, index) => @setActiveTab(index)
|
||||
@editor.on 'editor:edit-session-added', (e, editSession) => @addTabForEditSession(editSession)
|
||||
@editor.on 'editor:edit-session-removed', (e, editSession, index) => @removeTabAtIndex(index)
|
||||
|
||||
@on 'click', '.tab', (e) =>
|
||||
@editor.setActiveEditSessionIndex($(e.target).closest('.tab').index())
|
||||
|
||||
addTabForEditSession: (editSession) ->
|
||||
@append(new Tab(editSession))
|
||||
|
||||
setActiveTab: (index) ->
|
||||
@find(".tab.active").removeClass('active')
|
||||
@find(".tab:eq(#{index})").addClass('active')
|
||||
|
||||
removeTabAtIndex: (index) ->
|
||||
@find(".tab:eq(#{index})").remove()
|
||||
26
src/extensions/tabs/src/tabs.css
Normal file
26
src/extensions/tabs/src/tabs.css
Normal file
@@ -0,0 +1,26 @@
|
||||
.tabs {
|
||||
background: #222;
|
||||
border-bottom: 4px solid #555;
|
||||
}
|
||||
|
||||
.tab {
|
||||
cursor: default;
|
||||
float: left;
|
||||
margin: 4px;
|
||||
margin-bottom: 0;
|
||||
margin-right: 0;
|
||||
padding: 4px;
|
||||
background: #3a3a3a;
|
||||
color: white;
|
||||
-webkit-border-top-left-radius: 4px;
|
||||
-webkit-border-top-right-radius: 4px;
|
||||
}
|
||||
|
||||
|
||||
.tab.active {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
.tab:last-child {
|
||||
margin-right: 4px;
|
||||
}
|
||||
Reference in New Issue
Block a user