mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-09-19 15:08:08 +03:00
Merge remote-tracking branch 'origin/master' into cj-async-file-open
Conflicts: package.json
This commit is contained in:
commit
29a9a9d2ed
@ -54,6 +54,7 @@ module.exports = (grunt) ->
|
||||
glob_to_multiple:
|
||||
expand: true
|
||||
src: [
|
||||
'menus/*.cson'
|
||||
'keymaps/*.cson'
|
||||
'static/**/*.cson'
|
||||
]
|
||||
|
1
atom.sh
1
atom.sh
@ -32,6 +32,7 @@ done
|
||||
|
||||
if [ $EXPECT_OUTPUT ]; then
|
||||
$ATOM_BINARY --executed-from="$(pwd)" --pid=$$ $@
|
||||
exit $?
|
||||
else
|
||||
open -a $ATOM_PATH -n --args --executed-from="$(pwd)" --pid=$$ $@
|
||||
fi
|
||||
|
@ -1,5 +1,9 @@
|
||||
require '../src/window'
|
||||
Atom = require '../src/atom'
|
||||
window.atom = new Atom()
|
||||
atom = new Atom()
|
||||
atom.show() unless atom.getLoadSettings().exitWhenDone
|
||||
window.atom = atom
|
||||
|
||||
{runSpecSuite} = require '../spec/jasmine-helper'
|
||||
|
||||
atom.openDevTools()
|
||||
|
@ -1,11 +1,9 @@
|
||||
require '../spec/spec-helper'
|
||||
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
{Point} = require 'telepath'
|
||||
Project = require 'project'
|
||||
fsUtils = require 'fs-utils'
|
||||
TokenizedBuffer = require 'tokenized-buffer'
|
||||
{$, _, Point, fs} = require 'atom'
|
||||
Project = require '../src/project'
|
||||
fsUtils = require '../src/fs-utils'
|
||||
TokenizedBuffer = require '../src/tokenized-buffer'
|
||||
|
||||
defaultCount = 100
|
||||
window.pbenchmark = (args...) -> window.benchmark(args..., profile: true)
|
||||
@ -13,7 +11,7 @@ window.fbenchmark = (args...) -> window.benchmark(args..., focused: true)
|
||||
window.fpbenchmark = (args...) -> window.benchmark(args..., profile: true, focused: true)
|
||||
window.pfbenchmark = window.fpbenchmark
|
||||
|
||||
window.benchmarkFixturesProject = new Project(fsUtils.resolveOnLoadPath('benchmark/fixtures'))
|
||||
window.benchmarkFixturesProject = new Project(fsUtils.resolveOnLoadPath('../benchmark/fixtures'))
|
||||
|
||||
beforeEach ->
|
||||
window.project = window.benchmarkFixturesProject
|
||||
|
@ -1,8 +1,6 @@
|
||||
require './benchmark-helper'
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
TokenizedBuffer = require 'tokenized-buffer'
|
||||
RootView = require 'root-view'
|
||||
{$, _, RootView} = require 'atom'
|
||||
TokenizedBuffer = require '../src/tokenized-buffer'
|
||||
|
||||
describe "editor.", ->
|
||||
editor = null
|
||||
@ -12,7 +10,6 @@ describe "editor.", ->
|
||||
window.rootView = new RootView
|
||||
window.rootView.attachToDom()
|
||||
|
||||
|
||||
rootView.width(1024)
|
||||
rootView.height(768)
|
||||
rootView.open() # open blank editor
|
||||
@ -62,6 +59,116 @@ describe "editor.", ->
|
||||
editor.insertText('"')
|
||||
editor.backspace()
|
||||
|
||||
describe "empty-vs-set-innerHTML.", ->
|
||||
[firstRow, lastRow] = []
|
||||
beforeEach ->
|
||||
firstRow = editor.getFirstVisibleScreenRow()
|
||||
lastRow = editor.getLastVisibleScreenRow()
|
||||
|
||||
benchmark "build-gutter-html.", 1000, ->
|
||||
editor.gutter.renderLineNumbers(null, firstRow, lastRow)
|
||||
|
||||
benchmark "set-innerHTML.", 1000, ->
|
||||
editor.gutter.renderLineNumbers(null, firstRow, lastRow)
|
||||
editor.gutter.lineNumbers[0].innerHtml = ''
|
||||
|
||||
benchmark "empty.", 1000, ->
|
||||
editor.gutter.renderLineNumbers(null, firstRow, lastRow)
|
||||
editor.gutter.lineNumbers.empty()
|
||||
|
||||
describe "positionLeftForLineAndColumn.", ->
|
||||
line = null
|
||||
beforeEach ->
|
||||
editor.scrollTop(2000)
|
||||
editor.resetDisplay()
|
||||
line = editor.lineElementForScreenRow(106)[0]
|
||||
|
||||
describe "one-line.", ->
|
||||
beforeEach ->
|
||||
editor.clearCharacterWidthCache()
|
||||
|
||||
benchmark "uncached", 5000, ->
|
||||
editor.positionLeftForLineAndColumn(line, 106, 82)
|
||||
editor.clearCharacterWidthCache()
|
||||
|
||||
benchmark "cached", 5000, ->
|
||||
editor.positionLeftForLineAndColumn(line, 106, 82)
|
||||
|
||||
describe "multiple-lines.", ->
|
||||
[firstRow, lastRow] = []
|
||||
beforeEach ->
|
||||
firstRow = editor.getFirstVisibleScreenRow()
|
||||
lastRow = editor.getLastVisibleScreenRow()
|
||||
|
||||
benchmark "cache-entire-visible-area", 100, ->
|
||||
for i in [firstRow..lastRow]
|
||||
line = editor.lineElementForScreenRow(i)[0]
|
||||
editor.positionLeftForLineAndColumn(line, i, Math.max(0, editor.lineLengthForBufferRow(i)))
|
||||
|
||||
describe "text-rendering.", ->
|
||||
beforeEach ->
|
||||
editor.scrollTop(2000)
|
||||
|
||||
benchmark "resetDisplay", 50, ->
|
||||
editor.resetDisplay()
|
||||
|
||||
benchmark "htmlForScreenRows", 1000, ->
|
||||
lastRow = editor.getLastScreenRow()
|
||||
editor.htmlForScreenRows(0, lastRow)
|
||||
|
||||
benchmark "htmlForScreenRows.htmlParsing", 50, ->
|
||||
lastRow = editor.getLastScreenRow()
|
||||
html = editor.htmlForScreenRows(0, lastRow)
|
||||
|
||||
div = document.createElement('div')
|
||||
div.innerHTML = html
|
||||
|
||||
describe "gutter-api.", ->
|
||||
describe "getLineNumberElementsForClass.", ->
|
||||
beforeEach ->
|
||||
editor.gutter.addClassToLine(20, 'omgwow')
|
||||
editor.gutter.addClassToLine(40, 'omgwow')
|
||||
|
||||
benchmark "DOM", 20000, ->
|
||||
editor.gutter.getLineNumberElementsForClass('omgwow')
|
||||
|
||||
benchmark "getLineNumberElement.DOM", 20000, ->
|
||||
editor.gutter.getLineNumberElement(12)
|
||||
|
||||
benchmark "toggle-class", 2000, ->
|
||||
editor.gutter.addClassToLine(40, 'omgwow')
|
||||
editor.gutter.removeClassFromLine(40, 'omgwow')
|
||||
|
||||
describe "find-then-unset.", ->
|
||||
classes = ['one', 'two', 'three', 'four']
|
||||
|
||||
benchmark "single-class", 200, ->
|
||||
editor.gutter.addClassToLine(30, 'omgwow')
|
||||
editor.gutter.addClassToLine(40, 'omgwow')
|
||||
editor.gutter.removeClassFromAllLines('omgwow')
|
||||
|
||||
benchmark "multiple-class", 200, ->
|
||||
editor.gutter.addClassToLine(30, 'one')
|
||||
editor.gutter.addClassToLine(30, 'two')
|
||||
|
||||
editor.gutter.addClassToLine(40, 'two')
|
||||
editor.gutter.addClassToLine(40, 'three')
|
||||
editor.gutter.addClassToLine(40, 'four')
|
||||
|
||||
for klass in classes
|
||||
editor.gutter.removeClassFromAllLines(klass)
|
||||
|
||||
describe "line-htmlification.", ->
|
||||
div = null
|
||||
html = null
|
||||
beforeEach ->
|
||||
lastRow = editor.getLastScreenRow()
|
||||
html = editor.htmlForScreenRows(0, lastRow)
|
||||
div = document.createElement('div')
|
||||
|
||||
benchmark "setInnerHTML", 1, ->
|
||||
div.innerHTML = html
|
||||
|
||||
describe "9000-line-file.", ->
|
||||
benchmark "opening.", 5, ->
|
||||
rootView.open('huge.js')
|
||||
|
63
menus/base.cson
Normal file
63
menus/base.cson
Normal file
@ -0,0 +1,63 @@
|
||||
'menu': [
|
||||
{
|
||||
label: 'Atom'
|
||||
submenu: [
|
||||
{ label: 'About Atom', command: 'application:about' }
|
||||
{ label: "Version #{@version}", enabled: false }
|
||||
{ label: "Install update", command: 'application:install-update', visible: false }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Preferences...', command: 'application:show-settings' }
|
||||
{ label: 'Hide Atom', command: 'application:hide' }
|
||||
{ label: 'Hide Others', command: 'application:hide-other-applications' }
|
||||
{ label: 'Show All', command: 'application:unhide-all-applications' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Run Atom Specs', command: 'application:run-all-specs' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Quit', command: 'application:quit' }
|
||||
]
|
||||
}
|
||||
{
|
||||
label: 'File'
|
||||
submenu: [
|
||||
{ label: 'New Window', command: 'application:new-window' }
|
||||
{ label: 'New File', command: 'application:new-file' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Open...', command: 'application:open' }
|
||||
{ label: 'Open In Dev Mode...', command: 'application:open-dev' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Close Window', command: 'window:close' }
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
label: 'Edit'
|
||||
submenu: [
|
||||
{ label: 'Undo', command: 'core:undo' }
|
||||
{ label: 'Redo', command: 'core:redo' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Cut', command: 'core:cut' }
|
||||
{ label: 'Copy', command: 'core:copy' }
|
||||
{ label: 'Paste', command: 'core:paste' }
|
||||
{ label: 'Select All', command: 'core:select-all' }
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
label: 'View'
|
||||
submenu: [
|
||||
{ label: 'Reload', command: 'window:reload' }
|
||||
{ label: 'Toggle Full Screen', command: 'window:toggle-full-screen' }
|
||||
{ label: 'Toggle Developer Tools', command: 'window:toggle-dev-tools' }
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
label: 'Window'
|
||||
submenu: [
|
||||
{ label: 'Minimize', command: 'application:minimize' }
|
||||
{ label: 'Zoom', command: 'application:zoom' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Bring All to Front', command: 'application:bring-all-windows-to-front' }
|
||||
]
|
||||
}
|
||||
]
|
38
package.json
38
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "atom",
|
||||
"version": "28.0.0",
|
||||
"version": "31.0.0",
|
||||
"main": "./src/main.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -9,14 +9,14 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/atom/atom/issues"
|
||||
},
|
||||
"atomShellVersion": "0.5.3",
|
||||
"atomShellVersion": "0.6.0",
|
||||
"dependencies": {
|
||||
"async": "0.2.6",
|
||||
"bootstrap": "git://github.com/twbs/bootstrap.git#v3.0.0",
|
||||
"coffee-script": "1.6.2",
|
||||
"coffeestack": "0.6.0",
|
||||
"first-mate": "0.2.0",
|
||||
"git-utils": "0.25.0",
|
||||
"git-utils": "0.26.0",
|
||||
"guid": "0.0.10",
|
||||
"jasmine-focused": "~0.14.0",
|
||||
"mkdirp": "0.3.5",
|
||||
@ -28,8 +28,9 @@
|
||||
"pathwatcher": "0.5.0",
|
||||
"pegjs": "0.7.0",
|
||||
"plist": "git://github.com/nathansobo/node-plist.git",
|
||||
"q": "0.9.7",
|
||||
"rimraf": "2.1.4",
|
||||
"scandal": "0.4.0",
|
||||
"scandal": "0.5.0",
|
||||
"season": "0.13.0",
|
||||
"semver": "1.1.4",
|
||||
"space-pen": "1.3.0",
|
||||
@ -37,7 +38,6 @@
|
||||
"telepath": "0.8.1",
|
||||
"temp": "0.5.0",
|
||||
"underscore": "1.4.4",
|
||||
"q": "0.9.7",
|
||||
|
||||
"atom-light-ui": "0.4.0",
|
||||
"atom-light-syntax": "0.4.0",
|
||||
@ -48,17 +48,17 @@
|
||||
"archive-view": "0.8.0",
|
||||
"autocomplete": "0.6.0",
|
||||
"autoflow": "0.3.0",
|
||||
"bookmarks": "0.4.0",
|
||||
"bracket-matcher": "0.5.0",
|
||||
"collaboration": "0.19.0",
|
||||
"bookmarks": "0.5.0",
|
||||
"bracket-matcher": "0.6.0",
|
||||
"collaboration": "0.21.0",
|
||||
"command-logger": "0.4.0",
|
||||
"command-palette": "0.4.0",
|
||||
"editor-stats": "0.3.0",
|
||||
"exception-reporting": "0.2.0",
|
||||
"find-and-replace": "0.22.0",
|
||||
"fuzzy-finder": "0.7.0",
|
||||
"exception-reporting": "0.4.0",
|
||||
"find-and-replace": "0.24.0",
|
||||
"fuzzy-finder": "0.8.0",
|
||||
"gfm": "0.5.0",
|
||||
"git-diff": "0.4.0",
|
||||
"git-diff": "0.5.0",
|
||||
"gists": "0.3.0",
|
||||
"github-sign-in": "0.7.0",
|
||||
"go-to-line": "0.4.0",
|
||||
@ -66,24 +66,23 @@
|
||||
"image-view": "0.6.0",
|
||||
"link": "0.4.0",
|
||||
"markdown-preview": "0.6.0",
|
||||
"metrics": "0.7.0",
|
||||
"metrics": "0.8.0",
|
||||
"package-generator": "0.10.0",
|
||||
"release-notes": "0.2.0",
|
||||
"settings-view": "0.26.0",
|
||||
"release-notes": "0.3.0",
|
||||
"settings-view": "0.27.0",
|
||||
"snippets": "0.6.0",
|
||||
"spell-check": "0.6.0",
|
||||
"status-bar": "0.8.0",
|
||||
"status-bar": "0.9.0",
|
||||
"symbols-view": "0.8.0",
|
||||
"tabs": "0.5.0",
|
||||
"terminal": "0.10.0",
|
||||
"timecop": "0.5.0",
|
||||
"to-the-hubs": "0.4.0",
|
||||
"to-the-hubs": "0.6.0",
|
||||
"toml": "0.3.0",
|
||||
"tree-view": "0.7.0",
|
||||
"tree-view": "0.10.0",
|
||||
"ui-demo": "0.8.0",
|
||||
"whitespace": "0.5.0",
|
||||
"wrap-guide": "0.3.0",
|
||||
|
||||
"c-tmbundle": "1.0.0",
|
||||
"coffee-script-tmbundle": "1.0.0",
|
||||
"css-tmbundle": "1.0.0",
|
||||
@ -118,6 +117,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"biscotto": "0.0.17",
|
||||
"formidable": "~1.0.14",
|
||||
"fstream": "0.1.24",
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-cli": "~0.1.9",
|
||||
|
@ -7,6 +7,12 @@ cd "$(dirname "$0")/.."
|
||||
rm -rf ~/.atom
|
||||
git clean -dff
|
||||
|
||||
ATOM_CREDENTIALS_FILE=/var/lib/jenkins/config/atomcredentials
|
||||
if [ -f $ATOM_CREDENTIALS_FILE ]; then
|
||||
. $ATOM_CREDENTIALS_FILE
|
||||
export ATOM_ACCESS_TOKEN=$ATOM_ACCESS_TOKEN # make it visibile to grunt.
|
||||
fi
|
||||
|
||||
./script/bootstrap
|
||||
./node_modules/.bin/apm clean
|
||||
./node_modules/.bin/grunt ci --stack --no-color
|
||||
|
@ -151,7 +151,9 @@ describe "the `atom` global", ->
|
||||
expect(keymap.bindingsForElement(element3)['ctrl-y']).toBeUndefined()
|
||||
|
||||
describe "menu loading", ->
|
||||
beforeEach -> atom.contextMenu.definitions = []
|
||||
beforeEach ->
|
||||
atom.contextMenu.definitions = []
|
||||
atom.menu.template = []
|
||||
|
||||
describe "when the metadata does not contain a 'menus' manifest", ->
|
||||
it "loads all the .cson/.json files in the menus directory", ->
|
||||
@ -161,6 +163,8 @@ describe "the `atom` global", ->
|
||||
|
||||
atom.activatePackage("package-with-menus")
|
||||
|
||||
expect(atom.menu.template[0].label).toBe "Second to Last"
|
||||
expect(atom.menu.template[1].label).toBe "Last"
|
||||
expect(atom.contextMenu.definitionsForElement(element)[0].label).toBe "Menu item 1"
|
||||
expect(atom.contextMenu.definitionsForElement(element)[1].label).toBe "Menu item 2"
|
||||
expect(atom.contextMenu.definitionsForElement(element)[2].label).toBe "Menu item 3"
|
||||
@ -173,6 +177,8 @@ describe "the `atom` global", ->
|
||||
|
||||
atom.activatePackage("package-with-menus-manifest")
|
||||
|
||||
expect(atom.menu.template[0].label).toBe "Second to Last"
|
||||
expect(atom.menu.template[1].label).toBe "Last"
|
||||
expect(atom.contextMenu.definitionsForElement(element)[0].label).toBe "Menu item 2"
|
||||
expect(atom.contextMenu.definitionsForElement(element)[1].label).toBe "Menu item 1"
|
||||
expect(atom.contextMenu.definitionsForElement(element)[2]).toBeUndefined()
|
||||
|
@ -119,21 +119,21 @@ describe "Editor", ->
|
||||
|
||||
it "updates the rendered lines, cursors, selections, scroll position, and event subscriptions to match the given edit session", ->
|
||||
editor.attachToDom(heightInLines: 5, widthInChars: 30)
|
||||
editor.setCursorBufferPosition([3, 5])
|
||||
editor.setCursorBufferPosition([6, 13])
|
||||
editor.scrollToBottom()
|
||||
editor.scrollLeft(150)
|
||||
previousScrollHeight = editor.verticalScrollbar.prop('scrollHeight')
|
||||
previousScrollTop = editor.scrollTop()
|
||||
previousScrollLeft = editor.scrollLeft()
|
||||
|
||||
newEditSession.setScrollTop(120)
|
||||
newEditSession.setScrollTop(900)
|
||||
newEditSession.setSelectedBufferRange([[40, 0], [43, 1]])
|
||||
|
||||
editor.edit(newEditSession)
|
||||
{ firstRenderedScreenRow, lastRenderedScreenRow } = editor
|
||||
expect(editor.lineElementForScreenRow(firstRenderedScreenRow).text()).toBe newBuffer.lineForRow(firstRenderedScreenRow)
|
||||
expect(editor.lineElementForScreenRow(lastRenderedScreenRow).text()).toBe newBuffer.lineForRow(editor.lastRenderedScreenRow)
|
||||
expect(editor.scrollTop()).toBe 120
|
||||
expect(editor.scrollTop()).toBe 900
|
||||
expect(editor.scrollLeft()).toBe 0
|
||||
expect(editor.getSelectionView().regions[0].position().top).toBe 40 * editor.lineHeight
|
||||
editor.insertText("hello")
|
||||
@ -146,9 +146,9 @@ describe "Editor", ->
|
||||
expect(editor.verticalScrollbar.prop('scrollHeight')).toBe previousScrollHeight
|
||||
expect(editor.scrollTop()).toBe previousScrollTop
|
||||
expect(editor.scrollLeft()).toBe previousScrollLeft
|
||||
expect(editor.getCursorView().position()).toEqual { top: 3 * editor.lineHeight, left: 5 * editor.charWidth }
|
||||
expect(editor.getCursorView().position()).toEqual { top: 6 * editor.lineHeight, left: 13 * editor.charWidth }
|
||||
editor.insertText("goodbye")
|
||||
expect(editor.lineElementForScreenRow(3).text()).toMatch /^ vgoodbyear/
|
||||
expect(editor.lineElementForScreenRow(6).text()).toMatch /^ currentgoodbye/
|
||||
|
||||
it "triggers alert if edit session's buffer goes into conflict with changes on disk", ->
|
||||
filePath = "/tmp/atom-changed-file.txt"
|
||||
@ -1107,8 +1107,8 @@ describe "Editor", ->
|
||||
expect(span0.children('span:eq(2)')).toMatchSelector '.meta.brace.curly.js'
|
||||
expect(span0.children('span:eq(2)').text()).toBe "{"
|
||||
|
||||
line12 = editor.renderedLines.find('.line:eq(11)')
|
||||
expect(line12.find('span:eq(2)')).toMatchSelector '.keyword'
|
||||
line12 = editor.renderedLines.find('.line:eq(11)').children('span:eq(0)')
|
||||
expect(line12.children('span:eq(1)')).toMatchSelector '.keyword'
|
||||
|
||||
it "wraps hard tabs in a span", ->
|
||||
editor.setText('\t<- hard tab')
|
||||
@ -1123,12 +1123,13 @@ describe "Editor", ->
|
||||
expect(span0_0).toMatchSelector '.leading-whitespace'
|
||||
expect(span0_0.text()).toBe ' '
|
||||
|
||||
it "wraps trailing whitespace in a span", ->
|
||||
editor.setText('trailing whitespace -> ')
|
||||
line0 = editor.renderedLines.find('.line:first')
|
||||
span0_last = line0.children('span:eq(0)').children('span:last')
|
||||
expect(span0_last).toMatchSelector '.trailing-whitespace'
|
||||
expect(span0_last.text()).toBe ' '
|
||||
describe "when the line has trailing whitespace", ->
|
||||
it "wraps trailing whitespace in a span", ->
|
||||
editor.setText('trailing whitespace -> ')
|
||||
line0 = editor.renderedLines.find('.line:first')
|
||||
span0_last = line0.children('span:eq(0)').children('span:last')
|
||||
expect(span0_last).toMatchSelector '.trailing-whitespace'
|
||||
expect(span0_last.text()).toBe ' '
|
||||
|
||||
describe "when lines are updated in the buffer", ->
|
||||
it "syntax highlights the updated lines", ->
|
||||
@ -1878,13 +1879,55 @@ describe "Editor", ->
|
||||
# doesn't allow regular editors to set grammars
|
||||
expect(-> editor.setGrammar()).toThrow()
|
||||
|
||||
|
||||
describe "when config.editor.showLineNumbers is false", ->
|
||||
it "doesn't render any line numbers", ->
|
||||
expect(editor.gutter.lineNumbers).toBeVisible()
|
||||
config.set("editor.showLineNumbers", false)
|
||||
expect(editor.gutter.lineNumbers).not.toBeVisible()
|
||||
|
||||
describe "using gutter's api", ->
|
||||
it "can get all the line number elements", ->
|
||||
elements = editor.gutter.getLineNumberElements()
|
||||
len = editor.gutter.lastScreenRow - editor.gutter.firstScreenRow + 1
|
||||
expect(elements).toHaveLength(len)
|
||||
|
||||
it "can get a single line number element", ->
|
||||
element = editor.gutter.getLineNumberElement(3)
|
||||
expect(element).toBeTruthy()
|
||||
|
||||
it "returns falsy when there is no line element", ->
|
||||
expect(editor.gutter.getLineNumberElement(42)).toHaveLength 0
|
||||
|
||||
it "can add and remove classes to all the line numbers", ->
|
||||
wasAdded = editor.gutter.addClassToAllLines('heyok')
|
||||
expect(wasAdded).toBe true
|
||||
|
||||
elements = editor.gutter.getLineNumberElementsForClass('heyok')
|
||||
expect($(elements)).toHaveClass('heyok')
|
||||
|
||||
editor.gutter.removeClassFromAllLines('heyok')
|
||||
expect($(editor.gutter.getLineNumberElements())).not.toHaveClass('heyok')
|
||||
|
||||
it "can add and remove classes from a single line number", ->
|
||||
wasAdded = editor.gutter.addClassToLine(3, 'heyok')
|
||||
expect(wasAdded).toBe true
|
||||
|
||||
element = editor.gutter.getLineNumberElement(2)
|
||||
expect($(element)).not.toHaveClass('heyok')
|
||||
|
||||
it "can fetch line numbers by their class", ->
|
||||
editor.gutter.addClassToLine(1, 'heyok')
|
||||
editor.gutter.addClassToLine(3, 'heyok')
|
||||
|
||||
elements = editor.gutter.getLineNumberElementsForClass('heyok')
|
||||
expect(elements.length).toBe 2
|
||||
|
||||
expect($(elements[0])).toHaveClass 'line-number-1'
|
||||
expect($(elements[0])).toHaveClass 'heyok'
|
||||
|
||||
expect($(elements[1])).toHaveClass 'line-number-3'
|
||||
expect($(elements[1])).toHaveClass 'heyok'
|
||||
|
||||
describe "gutter line highlighting", ->
|
||||
beforeEach ->
|
||||
editor.attachToDom(heightInLines: 5.5)
|
||||
@ -2160,10 +2203,21 @@ describe "Editor", ->
|
||||
expect(editor.pixelPositionForBufferPosition([2,7])).toEqual top: 0, left: 0
|
||||
|
||||
describe "when the editor is attached and visible", ->
|
||||
it "returns the top and left pixel positions", ->
|
||||
beforeEach ->
|
||||
editor.attachToDom()
|
||||
|
||||
it "returns the top and left pixel positions", ->
|
||||
expect(editor.pixelPositionForBufferPosition([2,7])).toEqual top: 40, left: 70
|
||||
|
||||
it "caches the left position", ->
|
||||
editor.renderedLines.css('font-size', '16px')
|
||||
expect(editor.pixelPositionForBufferPosition([2,8])).toEqual top: 40, left: 80
|
||||
|
||||
# make characters smaller
|
||||
editor.renderedLines.css('font-size', '15px')
|
||||
|
||||
expect(editor.pixelPositionForBufferPosition([2,8])).toEqual top: 40, left: 80
|
||||
|
||||
describe "when clicking in the gutter", ->
|
||||
beforeEach ->
|
||||
editor.attachToDom()
|
||||
|
@ -1,3 +1,7 @@
|
||||
"context-menu":
|
||||
".test-1":
|
||||
"Menu item 1": "command-1"
|
||||
'menu': [
|
||||
{ 'label': 'Last' }
|
||||
]
|
||||
|
||||
'context-menu':
|
||||
'.test-1':
|
||||
'Menu item 1': 'command-1'
|
||||
|
@ -1,3 +1,7 @@
|
||||
"context-menu":
|
||||
".test-1":
|
||||
"Menu item 2": "command-2"
|
||||
'menu': [
|
||||
{ 'label': 'Second to Last' }
|
||||
]
|
||||
|
||||
'context-menu':
|
||||
'.test-1':
|
||||
'Menu item 2': 'command-2'
|
||||
|
@ -1,3 +1,3 @@
|
||||
"context-menu":
|
||||
".test-1":
|
||||
"Menu item 3": "command-3"
|
||||
'context-menu':
|
||||
'.test-1':
|
||||
'Menu item 3': 'command-3'
|
||||
|
@ -1,3 +1,7 @@
|
||||
"context-menu":
|
||||
".test-1":
|
||||
"Menu item 1": "command-1"
|
||||
'menu': [
|
||||
{ 'label': 'Second to Last' }
|
||||
]
|
||||
|
||||
'context-menu':
|
||||
'.test-1':
|
||||
'Menu item 1': 'command-1'
|
||||
|
@ -1,3 +1,7 @@
|
||||
"context-menu":
|
||||
".test-1":
|
||||
"Menu item 2": "command-2"
|
||||
'menu': [
|
||||
{ 'label': 'Last' }
|
||||
]
|
||||
|
||||
'context-menu':
|
||||
'.test-1':
|
||||
'Menu item 2': 'command-2'
|
||||
|
@ -1,3 +1,3 @@
|
||||
"context-menu":
|
||||
".test-1":
|
||||
"Menu item 3": "command-3"
|
||||
'context-menu':
|
||||
'.test-1':
|
||||
'Menu item 3': 'command-3'
|
||||
|
@ -2,7 +2,7 @@ try
|
||||
require '../src/window'
|
||||
Atom = require '../src/atom'
|
||||
window.atom = new Atom()
|
||||
window.atom.show()
|
||||
window.atom.show() unless atom.getLoadSettings().exitWhenDone
|
||||
{runSpecSuite} = require './jasmine-helper'
|
||||
|
||||
document.title = "Spec Suite"
|
||||
|
@ -18,6 +18,7 @@ atom.themes.loadBaseStylesheets()
|
||||
atom.themes.requireStylesheet '../static/jasmine'
|
||||
|
||||
fixturePackagesPath = path.resolve(__dirname, './fixtures/packages')
|
||||
atom.packages.packageDirPaths.unshift(fixturePackagesPath)
|
||||
atom.keymap.loadBundledKeymaps()
|
||||
[bindingSetsToRestore, bindingSetsByFirstKeystrokeToRestore] = []
|
||||
|
||||
@ -49,8 +50,13 @@ beforeEach ->
|
||||
bindingSetsToRestore = _.clone(keymap.bindingSets)
|
||||
bindingSetsByFirstKeystrokeToRestore = _.clone(keymap.bindingSetsByFirstKeystroke)
|
||||
|
||||
# prevent specs from modifying Atom's menus
|
||||
spyOn(atom.menu, 'sendToBrowserProcess')
|
||||
|
||||
# reset config before each spec; don't load or save from/to `config.json`
|
||||
config = new Config()
|
||||
config = new Config
|
||||
resourcePath: window.resourcePath
|
||||
configDirPath: atom.getConfigDirPath()
|
||||
config.packageDirPaths.unshift(fixturePackagesPath)
|
||||
spyOn(config, 'load')
|
||||
spyOn(config, 'save')
|
||||
|
@ -18,11 +18,13 @@ class ApplicationMenu
|
||||
|
||||
# Public: Updates the entire menu with the given keybindings.
|
||||
#
|
||||
# * template:
|
||||
# The Object which describes the menu to display.
|
||||
# * keystrokesByCommand:
|
||||
# An Object where the keys are commands and the values are Arrays containing
|
||||
# the keystrokes.
|
||||
update: (keystrokesByCommand) ->
|
||||
template = @getTemplate(keystrokesByCommand)
|
||||
update: (template, keystrokesByCommand) ->
|
||||
@translateTemplate template, keystrokesByCommand
|
||||
@menu = Menu.buildFromTemplate(template)
|
||||
Menu.setApplicationMenu(@menu)
|
||||
|
||||
@ -77,81 +79,6 @@ class ApplicationMenu
|
||||
]
|
||||
]
|
||||
|
||||
# Private: The complete list of menu items.
|
||||
#
|
||||
# * keystrokesByCommand:
|
||||
# An Object where the keys are commands and the values are Arrays containing
|
||||
# the keystrokes.
|
||||
#
|
||||
# Returns a complete menu configuration Object for use with atom-shell's
|
||||
# native menu API.
|
||||
getTemplate: (keystrokesByCommand) ->
|
||||
atomMenu =
|
||||
label: 'Atom'
|
||||
submenu: [
|
||||
{ label: 'About Atom', command: 'application:about' }
|
||||
{ label: "Version #{@version}", enabled: false }
|
||||
{ label: "Install update", command: 'application:install-update', visible: false }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Preferences...', command: 'application:show-settings' }
|
||||
{ label: 'Hide Atom', command: 'application:hide' }
|
||||
{ label: 'Hide Others', command: 'application:hide-other-applications' }
|
||||
{ label: 'Show All', command: 'application:unhide-all-applications' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Run Atom Specs', command: 'application:run-all-specs' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Quit', command: 'application:quit' }
|
||||
]
|
||||
|
||||
fileMenu =
|
||||
label: 'File'
|
||||
submenu: [
|
||||
{ label: 'New Window', command: 'application:new-window' }
|
||||
{ label: 'New File', command: 'application:new-file' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Open...', command: 'application:open' }
|
||||
{ label: 'Open In Dev Mode...', command: 'application:open-dev' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Close Window', command: 'window:close' }
|
||||
]
|
||||
|
||||
editMenu =
|
||||
label: 'Edit'
|
||||
submenu: [
|
||||
{ label: 'Undo', command: 'core:undo' }
|
||||
{ label: 'Redo', command: 'core:redo' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Cut', command: 'core:cut' }
|
||||
{ label: 'Copy', command: 'core:copy' }
|
||||
{ label: 'Paste', command: 'core:paste' }
|
||||
{ label: 'Select All', command: 'core:select-all' }
|
||||
]
|
||||
|
||||
viewMenu =
|
||||
label: 'View'
|
||||
submenu: [
|
||||
{ label: 'Reload', command: 'window:reload' }
|
||||
{ label: 'Toggle Full Screen', command: 'window:toggle-full-screen' }
|
||||
{ label: 'Toggle Developer Tools', command: 'window:toggle-dev-tools' }
|
||||
]
|
||||
|
||||
windowMenu =
|
||||
label: 'Window'
|
||||
submenu: [
|
||||
{ label: 'Minimize', command: 'application:minimize' }
|
||||
{ label: 'Zoom', command: 'application:zoom' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Bring All to Front', command: 'application:bring-all-windows-to-front' }
|
||||
]
|
||||
|
||||
devMenu =
|
||||
label: '\uD83D\uDC80' # Skull emoji
|
||||
submenu: [ { label: 'In Development Mode', enabled: false } ]
|
||||
|
||||
template = [atomMenu, fileMenu, editMenu, viewMenu, windowMenu]
|
||||
|
||||
@translateTemplate template, keystrokesByCommand
|
||||
|
||||
# Private: Combines a menu template with the appropriate keystrokes.
|
||||
#
|
||||
# * template:
|
||||
@ -193,7 +120,7 @@ class ApplicationMenu
|
||||
modifiers = modifiers.map (modifier) ->
|
||||
modifier.replace(/shift/ig, "Shift")
|
||||
.replace(/meta/ig, "Command")
|
||||
.replace(/ctrl/ig, "MacCtrl")
|
||||
.replace(/ctrl/ig, "Ctrl")
|
||||
.replace(/alt/ig, "Alt")
|
||||
|
||||
keys = modifiers.concat([key.toUpperCase()])
|
||||
|
@ -1,7 +1,6 @@
|
||||
AtomWindow = require 'atom-window'
|
||||
ApplicationMenu = require 'application-menu'
|
||||
AtomProtocolHandler = require 'atom-protocol-handler'
|
||||
BrowserWindow = require 'browser-window'
|
||||
Menu = require 'menu'
|
||||
autoUpdater = require 'auto-updater'
|
||||
app = require 'app'
|
||||
@ -34,7 +33,7 @@ class AtomApplication
|
||||
# take a few seconds to trigger 'error' event, it could be a bug of node
|
||||
# or atom-shell, before it's fixed we check the existence of socketPath to
|
||||
# speedup startup.
|
||||
if not fs.existsSync socketPath
|
||||
if (not fs.existsSync socketPath) or options.test
|
||||
createAtomApplication()
|
||||
return
|
||||
|
||||
@ -51,7 +50,8 @@ class AtomApplication
|
||||
resourcePath: null
|
||||
version: null
|
||||
|
||||
constructor: ({@resourcePath, pathsToOpen, urlsToOpen, @version, test, pidToKillWhenClosed, devMode, newWindow, specDirectory}) ->
|
||||
constructor: (options) ->
|
||||
{@resourcePath, @version, @devMode} = options
|
||||
global.atomApplication = this
|
||||
|
||||
@pidsToOpenWindows = {}
|
||||
@ -66,6 +66,10 @@ class AtomApplication
|
||||
@handleEvents()
|
||||
@checkForUpdates()
|
||||
|
||||
@openWithOptions(options)
|
||||
|
||||
# Private: Opens a new window based on the options provided.
|
||||
openWithOptions: ({pathsToOpen, urlsToOpen, test, pidToKillWhenClosed, devMode, newWindow, specDirectory}) ->
|
||||
if test
|
||||
@runSpecs({exitWhenDone: true, @resourcePath, specDirectory})
|
||||
else if pathsToOpen.length > 0
|
||||
@ -94,8 +98,7 @@ class AtomApplication
|
||||
fs.unlinkSync socketPath if fs.existsSync(socketPath)
|
||||
server = net.createServer (connection) =>
|
||||
connection.on 'data', (data) =>
|
||||
options = JSON.parse(data)
|
||||
@openPaths(options)
|
||||
@openWithOptions(JSON.parse(data))
|
||||
|
||||
server.listen socketPath
|
||||
server.on 'error', (error) -> console.error 'Application server failed', error
|
||||
@ -126,7 +129,8 @@ class AtomApplication
|
||||
@on 'application:hide', -> Menu.sendActionToFirstResponder('hide:')
|
||||
@on 'application:hide-other-applications', -> Menu.sendActionToFirstResponder('hideOtherApplications:')
|
||||
@on 'application:unhide-all-applications', -> Menu.sendActionToFirstResponder('unhideAllApplications:')
|
||||
@on 'application:new-window', -> @openPath()
|
||||
@on 'application:new-window', ->
|
||||
@openPath(initialSize: @getFocusedWindowSize())
|
||||
@on 'application:new-file', -> (@focusedWindow() ? this).openPath()
|
||||
@on 'application:open', -> @promptForPath()
|
||||
@on 'application:open-dev', -> @promptForPath(devMode: true)
|
||||
@ -144,7 +148,7 @@ class AtomApplication
|
||||
|
||||
app.on 'open-url', (event, urlToOpen) =>
|
||||
event.preventDefault()
|
||||
@openUrl(urlToOpen)
|
||||
@openUrl({urlToOpen, @devMode})
|
||||
|
||||
autoUpdater.on 'ready-for-update-on-quit', (event, version, quitAndUpdateCallback) =>
|
||||
event.preventDefault()
|
||||
@ -162,8 +166,8 @@ class AtomApplication
|
||||
else
|
||||
@promptForPath()
|
||||
|
||||
ipc.once 'update-application-menu', (processId, routingId, keystrokesByCommand) =>
|
||||
@applicationMenu.update(keystrokesByCommand)
|
||||
ipc.on 'update-application-menu', (processId, routingId, template, keystrokesByCommand) =>
|
||||
@applicationMenu.update(template, keystrokesByCommand)
|
||||
|
||||
ipc.on 'run-package-specs', (processId, routingId, specDirectory) =>
|
||||
@runSpecs({resourcePath: global.devResourcePath, specDirectory: specDirectory, exitWhenDone: false})
|
||||
@ -192,6 +196,17 @@ class AtomApplication
|
||||
focusedWindow: ->
|
||||
_.find @windows, (atomWindow) -> atomWindow.isFocused()
|
||||
|
||||
# Public: Get the height and width of the focused window.
|
||||
#
|
||||
# Returns an object with height and width keys or null if there is no
|
||||
# focused window.
|
||||
getFocusedWindowSize: ->
|
||||
if focusedWindow = @focusedWindow()
|
||||
[width, height] = focusedWindow.getSize()
|
||||
{width, height}
|
||||
else
|
||||
null
|
||||
|
||||
# Public: Opens multiple paths, in existing windows if possible.
|
||||
#
|
||||
# * options
|
||||
@ -217,10 +232,13 @@ class AtomApplication
|
||||
# Boolean of whether this should be opened in a new window.
|
||||
# + devMode:
|
||||
# Boolean to control the opened window's dev mode.
|
||||
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode}={}) ->
|
||||
[basename, initialLine] = path.basename(pathToOpen).split(':')
|
||||
pathToOpen = "#{path.dirname(pathToOpen)}/#{basename}"
|
||||
initialLine -= 1 if initialLine # Convert line numbers to a base of 0
|
||||
# + initialSize:
|
||||
# Object with height and width keys.
|
||||
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, initialSize}={}) ->
|
||||
if pathToOpen
|
||||
[basename, initialLine] = path.basename(pathToOpen).split(':')
|
||||
pathToOpen = "#{path.dirname(pathToOpen)}/#{basename}"
|
||||
initialLine -= 1 if initialLine # Convert line numbers to a base of 0
|
||||
|
||||
unless devMode
|
||||
existingWindow = @windowForPath(pathToOpen) unless pidToKillWhenClosed or newWindow
|
||||
@ -234,7 +252,7 @@ class AtomApplication
|
||||
else
|
||||
resourcePath = @resourcePath
|
||||
bootstrapScript = require.resolve('./window-bootstrap')
|
||||
openedWindow = new AtomWindow({pathToOpen, initialLine, bootstrapScript, resourcePath, devMode})
|
||||
openedWindow = new AtomWindow({pathToOpen, initialLine, bootstrapScript, resourcePath, devMode, initialSize})
|
||||
|
||||
if pidToKillWhenClosed?
|
||||
@pidsToOpenWindows[pidToKillWhenClosed] = openedWindow
|
||||
@ -248,9 +266,11 @@ class AtomApplication
|
||||
console.log("Killing process #{pid} failed: #{error.code}")
|
||||
delete @pidsToOpenWindows[pid]
|
||||
|
||||
# Private: Handles an atom:// url.
|
||||
# Private: Open an atom:// url.
|
||||
#
|
||||
# Currently only supports atom://session/<session-id> urls.
|
||||
# The host of the URL being opened is assumed to be the package name
|
||||
# responsible for opening the URL. A new window will be created with
|
||||
# that package's `urlMain` as the bootstrap script.
|
||||
#
|
||||
# * options
|
||||
# + urlToOpen:
|
||||
@ -258,15 +278,25 @@ class AtomApplication
|
||||
# + devMode:
|
||||
# Boolean to control the opened window's dev mode.
|
||||
openUrl: ({urlToOpen, devMode}) ->
|
||||
parsedUrl = url.parse(urlToOpen)
|
||||
if parsedUrl.host is 'session'
|
||||
sessionId = parsedUrl.path.split('/')[1]
|
||||
console.log "Joining session #{sessionId}"
|
||||
if sessionId
|
||||
bootstrapScript = 'collaboration/lib/bootstrap'
|
||||
new AtomWindow({bootstrapScript, @resourcePath, sessionId, devMode})
|
||||
unless @packages?
|
||||
PackageManager = require './package-manager'
|
||||
fsUtils = require './fs-utils'
|
||||
@packages = new PackageManager
|
||||
configDirPath: fsUtils.absolute('~/.atom')
|
||||
devMode: devMode
|
||||
resourcePath: @resourcePath
|
||||
|
||||
packageName = url.parse(urlToOpen).host
|
||||
pack = _.find @packages.getAvailablePackageMetadata(), ({name}) -> name is packageName
|
||||
if pack?
|
||||
if pack.urlMain
|
||||
packagePath = @packages.resolvePackagePath(packageName)
|
||||
bootstrapScript = path.resolve(packagePath, pack.urlMain)
|
||||
new AtomWindow({bootstrapScript, @resourcePath, devMode, urlToOpen, initialSize: getFocusedWindowSize()})
|
||||
else
|
||||
console.log "Package '#{pack.name}' does not have a url main: #{urlToOpen}"
|
||||
else
|
||||
console.log "Opening unknown url #{urlToOpen}"
|
||||
console.log "Opening unknown url: #{urlToOpen}"
|
||||
|
||||
# Private: Opens up a new {AtomWindow} to run specs within.
|
||||
#
|
||||
|
@ -90,8 +90,9 @@ class AtomPackage extends Package
|
||||
@stylesheetsActivated = true
|
||||
|
||||
activateResources: ->
|
||||
keymap.add(keymapPath, map) for [keymapPath, map] in @keymaps
|
||||
atom.keymap.add(keymapPath, map) for [keymapPath, map] in @keymaps
|
||||
atom.contextMenu.add(menuPath, map['context-menu']) for [menuPath, map] in @menus
|
||||
atom.menu.add(map.menu) for [menuPath, map] in @menus when map.menu
|
||||
syntax.addGrammar(grammar) for grammar in @grammars
|
||||
for [scopedPropertiesPath, selector, properties] in @scopedProperties
|
||||
syntax.addProperties(scopedPropertiesPath, selector, properties)
|
||||
@ -169,7 +170,7 @@ class AtomPackage extends Package
|
||||
deactivateResources: ->
|
||||
syntax.removeGrammar(grammar) for grammar in @grammars
|
||||
syntax.removeProperties(scopedPropertiesPath) for [scopedPropertiesPath] in @scopedProperties
|
||||
keymap.remove(keymapPath) for [keymapPath] in @keymaps
|
||||
atom.keymap.remove(keymapPath) for [keymapPath] in @keymaps
|
||||
atom.themes.removeStylesheet(stylesheetPath) for [stylesheetPath] in @stylesheets
|
||||
@stylesheetsActivated = false
|
||||
|
||||
|
@ -30,13 +30,13 @@ class AtomWindow
|
||||
loadSettings = _.extend({}, settings)
|
||||
loadSettings.windowState ?= ''
|
||||
loadSettings.initialPath = pathToOpen
|
||||
try
|
||||
if fs.statSync(pathToOpen).isFile()
|
||||
loadSettings.initialPath = path.dirname(pathToOpen)
|
||||
if fs.statSyncNoException(pathToOpen).isFile?()
|
||||
loadSettings.initialPath = path.dirname(pathToOpen)
|
||||
|
||||
@browserWindow.loadSettings = loadSettings
|
||||
@browserWindow.once 'window:loaded', => @loaded = true
|
||||
@browserWindow.loadUrl "file://#{@resourcePath}/static/index.html"
|
||||
@browserWindow.focusOnWebView() if @isSpec
|
||||
|
||||
@openPath(pathToOpen, initialLine)
|
||||
|
||||
@ -54,6 +54,8 @@ class AtomWindow
|
||||
false
|
||||
else if pathToCheck is initialPath
|
||||
true
|
||||
else if fs.statSyncNoException(pathToCheck).isDirectory?()
|
||||
false
|
||||
else if pathToCheck.indexOf(path.join(initialPath, path.sep)) is 0
|
||||
true
|
||||
else
|
||||
@ -98,16 +100,23 @@ class AtomWindow
|
||||
@browserWindow.once 'window:loaded', => @openPath(pathToOpen, initialLine)
|
||||
|
||||
sendCommand: (command, args...) ->
|
||||
if @handlesAtomCommands()
|
||||
@sendAtomCommand(command, args...)
|
||||
if @isSpecWindow()
|
||||
unless @sendCommandToFirstResponder(command)
|
||||
switch command
|
||||
when 'window:reload' then @reload()
|
||||
when 'window:toggle-dev-tools' then @toggleDevTools()
|
||||
when 'window:close' then @close()
|
||||
else if @isWebViewFocused()
|
||||
@sendCommandToBrowserWindow(command, args...)
|
||||
else
|
||||
@sendNativeCommand(command)
|
||||
unless @sendCommandToFirstResponder(command)
|
||||
@sendCommandToBrowserWindow(command, args...)
|
||||
|
||||
sendAtomCommand: (command, args...) ->
|
||||
sendCommandToBrowserWindow: (command, args...) ->
|
||||
action = if args[0]?.contextCommand then 'context-command' else 'command'
|
||||
ipc.sendChannel @browserWindow.getProcessId(), @browserWindow.getRoutingId(), action, command, args...
|
||||
|
||||
sendNativeCommand: (command) ->
|
||||
sendCommandToFirstResponder: (command) ->
|
||||
switch command
|
||||
when 'core:undo' then Menu.sendActionToFirstResponder('undo:')
|
||||
when 'core:redo' then Menu.sendActionToFirstResponder('redo:')
|
||||
@ -115,14 +124,15 @@ class AtomWindow
|
||||
when 'core:cut' then Menu.sendActionToFirstResponder('cut:')
|
||||
when 'core:paste' then Menu.sendActionToFirstResponder('paste:')
|
||||
when 'core:select-all' then Menu.sendActionToFirstResponder('selectAll:')
|
||||
when 'window:reload' then @reload()
|
||||
when 'window:toggle-dev-tools' then @toggleDevTools()
|
||||
when 'window:close' then @close()
|
||||
else return false
|
||||
true
|
||||
|
||||
close: -> @browserWindow.close()
|
||||
|
||||
focus: -> @browserWindow.focus()
|
||||
|
||||
getSize: -> @browserWindow.getSize()
|
||||
|
||||
handlesAtomCommands: ->
|
||||
not @isSpecWindow() and @isWebViewFocused()
|
||||
|
||||
|
@ -27,6 +27,9 @@ class Atom
|
||||
initialize: ->
|
||||
@unsubscribe()
|
||||
|
||||
{devMode, resourcePath} = atom.getLoadSettings()
|
||||
configDirPath = @getConfigDirPath()
|
||||
|
||||
Config = require './config'
|
||||
Keymap = require './keymap'
|
||||
PackageManager = require './package-manager'
|
||||
@ -34,8 +37,11 @@ class Atom
|
||||
Syntax = require './syntax'
|
||||
ThemeManager = require './theme-manager'
|
||||
ContextMenuManager = require './context-menu-manager'
|
||||
MenuManager = require './menu-manager'
|
||||
|
||||
@packages = new PackageManager()
|
||||
@config = new Config({configDirPath, resourcePath})
|
||||
@keymap = new Keymap()
|
||||
@packages = new PackageManager({devMode, configDirPath, resourcePath})
|
||||
|
||||
#TODO Remove once packages have been updated to not touch atom.packageStates directly
|
||||
@__defineGetter__ 'packageStates', => @packages.packageStates
|
||||
@ -43,10 +49,9 @@ class Atom
|
||||
|
||||
@subscribe @packages, 'loaded', => @watchThemes()
|
||||
@themes = new ThemeManager()
|
||||
@contextMenu = new ContextMenuManager(@getLoadSettings().devMode)
|
||||
@config = new Config()
|
||||
@contextMenu = new ContextMenuManager(devMode)
|
||||
@menu = new MenuManager()
|
||||
@pasteboard = new Pasteboard()
|
||||
@keymap = new Keymap()
|
||||
@syntax = deserialize(@getWindowState('syntax')) ? new Syntax()
|
||||
|
||||
getCurrentWindow: ->
|
||||
@ -83,17 +88,21 @@ class Atom
|
||||
else
|
||||
browserWindow.center()
|
||||
|
||||
restoreDimensions: (defaultDimensions={width: 800, height: 600})->
|
||||
restoreDimensions: ->
|
||||
dimensions = @getWindowState().getObject('dimensions')
|
||||
dimensions = defaultDimensions unless dimensions?.width and dimensions?.height
|
||||
unless dimensions?.width and dimensions?.height
|
||||
{height, width} = @getLoadSettings().initialSize ? {}
|
||||
height ?= screen.availHeight
|
||||
width ?= Math.min(screen.availWidth, 1024)
|
||||
dimensions = {width, height}
|
||||
@setDimensions(dimensions)
|
||||
|
||||
# Public: Get the load settings for the current window.
|
||||
#
|
||||
# Returns an object containing all the load setting key/value pairs.
|
||||
getLoadSettings: ->
|
||||
@loadSettings ?= _.clone(@getCurrentWindow().loadSettings)
|
||||
_.clone(@loadSettings)
|
||||
@loadSettings ?= _.deepClone(@getCurrentWindow().loadSettings)
|
||||
_.deepClone(@loadSettings)
|
||||
|
||||
deserializeProject: ->
|
||||
Project = require './project'
|
||||
@ -213,6 +222,10 @@ class Atom
|
||||
getHomeDirPath: ->
|
||||
app.getHomeDir()
|
||||
|
||||
# Public: Get the directory path to Atom's configuration area.
|
||||
getConfigDirPath: ->
|
||||
@configDirPath ?= fsUtils.absolute('~/.atom')
|
||||
|
||||
getWindowStatePath: ->
|
||||
switch @windowMode
|
||||
when 'spec'
|
||||
@ -244,7 +257,7 @@ class Atom
|
||||
documentStateJson = @getLoadSettings().windowState
|
||||
|
||||
try
|
||||
documentState = JSON.parse(documentStateJson) if documentStateJson?
|
||||
documentState = JSON.parse(documentStateJson) if documentStateJson
|
||||
catch error
|
||||
console.warn "Error parsing window state: #{windowStatePath}", error.stack, error
|
||||
|
||||
|
@ -26,10 +26,16 @@ compileCoffeeScript = (coffee, filePath, cachePath) ->
|
||||
fs.writeFileSync(cachePath, js)
|
||||
js
|
||||
|
||||
require.extensions['.coffee'] = (module, filePath) ->
|
||||
requireCoffeeScript = (module, filePath) ->
|
||||
coffee = fs.readFileSync(filePath, 'utf8')
|
||||
cachePath = getCachePath(coffee)
|
||||
js = getCachedJavaScript(cachePath) ? compileCoffeeScript(coffee, filePath, cachePath)
|
||||
module._compile(js, filePath)
|
||||
|
||||
module.exports = {cacheDir}
|
||||
module.exports =
|
||||
cacheDir: cacheDir
|
||||
register: ->
|
||||
Object.defineProperty(require.extensions, '.coffee', {
|
||||
writable: false
|
||||
value: requireCoffeeScript
|
||||
})
|
||||
|
@ -7,24 +7,22 @@ path = require 'path'
|
||||
async = require 'async'
|
||||
pathWatcher = require 'pathwatcher'
|
||||
|
||||
configDirPath = fsUtils.absolute("~/.atom")
|
||||
|
||||
# Public: Used to access all of Atom's configuration details.
|
||||
#
|
||||
# A global instance of this class is available to all plugins which can be
|
||||
# referenced using `global.config`
|
||||
# referenced using `atom.config`
|
||||
#
|
||||
# ### Best practices ###
|
||||
# ### Best practices
|
||||
#
|
||||
# * Create your own root keypath using your package's name.
|
||||
# * Don't depend on (or write to) configuration keys outside of your keypath.
|
||||
#
|
||||
# ### Example ###
|
||||
# ### Example
|
||||
#
|
||||
# ```coffeescript
|
||||
# global.config.set('myplugin.key', 'value')
|
||||
# global.observe 'myplugin.key', ->
|
||||
# console.log 'My configuration changed:', global.config.get('myplugin.key')
|
||||
# atom.config.set('myplugin.key', 'value')
|
||||
# atom.config.observe 'myplugin.key', ->
|
||||
# console.log 'My configuration changed:', atom.config.get('myplugin.key')
|
||||
# ```
|
||||
module.exports =
|
||||
class Config
|
||||
@ -35,27 +33,27 @@ class Config
|
||||
configFileHasErrors: null
|
||||
|
||||
# Private: Created during initialization, available as `global.config`
|
||||
constructor: ->
|
||||
@configDirPath = configDirPath
|
||||
@bundledKeymapsDirPath = path.join(resourcePath, "keymaps")
|
||||
@nodeModulesDirPath = path.join(resourcePath, "node_modules")
|
||||
constructor: ({@configDirPath, @resourcePath}={}) ->
|
||||
@bundledKeymapsDirPath = path.join(@resourcePath, "keymaps")
|
||||
@bundledMenusDirPath = path.join(resourcePath, "menus")
|
||||
@nodeModulesDirPath = path.join(@resourcePath, "node_modules")
|
||||
@bundledPackageDirPaths = [@nodeModulesDirPath]
|
||||
@lessSearchPaths = [
|
||||
path.join(resourcePath, 'static', 'variables')
|
||||
path.join(resourcePath, 'static')
|
||||
path.join(@resourcePath, 'static', 'variables')
|
||||
path.join(@resourcePath, 'static')
|
||||
]
|
||||
@packageDirPaths = [path.join(configDirPath, "packages")]
|
||||
@packageDirPaths = [path.join(@configDirPath, "packages")]
|
||||
if atom.getLoadSettings().devMode
|
||||
@packageDirPaths.unshift(path.join(configDirPath, "dev", "packages"))
|
||||
@packageDirPaths.unshift(path.join(@configDirPath, "dev", "packages"))
|
||||
@userPackageDirPaths = _.clone(@packageDirPaths)
|
||||
@userStoragePath = path.join(configDirPath, "storage")
|
||||
@userStoragePath = path.join(@configDirPath, "storage")
|
||||
|
||||
@defaultSettings =
|
||||
core: _.clone(require('./root-view').configDefaults)
|
||||
editor: _.clone(require('./editor').configDefaults)
|
||||
@settings = {}
|
||||
@configFilePath = fsUtils.resolve(configDirPath, 'config', ['json', 'cson'])
|
||||
@configFilePath ?= path.join(configDirPath, 'config.cson')
|
||||
@configFilePath = fsUtils.resolve(@configDirPath, 'config', ['json', 'cson'])
|
||||
@configFilePath ?= path.join(@configDirPath, 'config.cson')
|
||||
|
||||
# Private:
|
||||
initializeConfigDirectory: (done) ->
|
||||
@ -67,7 +65,7 @@ class Config
|
||||
fsUtils.copy(sourcePath, destinationPath, callback)
|
||||
queue.drain = done
|
||||
|
||||
templateConfigDirPath = fsUtils.resolve(window.resourcePath, 'dot-atom')
|
||||
templateConfigDirPath = fsUtils.resolve(@resourcePath, 'dot-atom')
|
||||
onConfigDirFile = (sourcePath) =>
|
||||
relativePath = sourcePath.substring(templateConfigDirPath.length + 1)
|
||||
destinationPath = path.join(@configDirPath, relativePath)
|
||||
|
@ -54,6 +54,14 @@ class CursorView extends View
|
||||
|
||||
@setVisible(@cursor.isVisible() and not @editor.isFoldedAtScreenRow(screenPosition.row))
|
||||
|
||||
# Override for speed. The base function checks the computedStyle
|
||||
isHidden: ->
|
||||
style = this[0].style
|
||||
if style.display == 'none' or not @isOnDom()
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
needsAutoscroll: ->
|
||||
@cursor.needsAutoscroll
|
||||
|
||||
|
@ -9,11 +9,16 @@ fsUtils = require './fs-utils'
|
||||
$ = require './jquery-extensions'
|
||||
_ = require './underscore-extensions'
|
||||
|
||||
MeasureRange = document.createRange()
|
||||
TextNodeFilter = { acceptNode: -> NodeFilter.FILTER_ACCEPT }
|
||||
NoScope = ['no-scope']
|
||||
|
||||
# Private: Represents the entire visual pane in Atom.
|
||||
#
|
||||
# The Editor manages the {EditSession}, which manages the file buffers.
|
||||
module.exports =
|
||||
class Editor extends View
|
||||
@characterWidthCache: {}
|
||||
@configDefaults:
|
||||
fontSize: 20
|
||||
showInvisibles: false
|
||||
@ -37,11 +42,11 @@ class Editor extends View
|
||||
_.extend(attributes, params.attributes) if params.attributes
|
||||
@div attributes, =>
|
||||
@subview 'gutter', new Gutter
|
||||
@input class: 'hidden-input', outlet: 'hiddenInput'
|
||||
@div class: 'scroll-view', outlet: 'scrollView', =>
|
||||
@div class: 'overlayer', outlet: 'overlayer'
|
||||
@div class: 'lines', outlet: 'renderedLines'
|
||||
@div class: 'underlayer', outlet: 'underlayer'
|
||||
@div class: 'underlayer', outlet: 'underlayer', =>
|
||||
@input class: 'hidden-input', outlet: 'hiddenInput'
|
||||
@div class: 'vertical-scrollbar', outlet: 'verticalScrollbar', =>
|
||||
@div outlet: 'verticalScrollbarContent'
|
||||
|
||||
@ -643,9 +648,9 @@ class Editor extends View
|
||||
@addClass 'is-focused'
|
||||
|
||||
@hiddenInput.on 'focusout', =>
|
||||
@bringHiddenInputIntoView()
|
||||
@isFocused = false
|
||||
@removeClass 'is-focused'
|
||||
@hiddenInput.offset(top: 0, left: 0)
|
||||
|
||||
@underlayer.on 'mousedown', (e) =>
|
||||
@renderedLines.trigger(e)
|
||||
@ -703,7 +708,12 @@ class Editor extends View
|
||||
@on 'cursor:moved', =>
|
||||
return unless @isFocused
|
||||
cursorView = @getCursorView()
|
||||
@hiddenInput.offset(cursorView.offset()) if cursorView.is(':visible')
|
||||
|
||||
if cursorView.isVisible()
|
||||
# This is an order of magnitude faster than checking .offset().
|
||||
style = cursorView[0].style
|
||||
@hiddenInput[0].style.top = style.top
|
||||
@hiddenInput[0].style.left = style.left
|
||||
|
||||
selectedText = null
|
||||
@hiddenInput.on 'compositionstart', =>
|
||||
@ -727,6 +737,9 @@ class Editor extends View
|
||||
@hiddenInput.val(lastInput)
|
||||
false
|
||||
|
||||
bringHiddenInputIntoView: ->
|
||||
@hiddenInput.css(top: @scrollTop(), left: @scrollLeft())
|
||||
|
||||
selectOnMousemoveUntilMouseup: ->
|
||||
lastMoveEvent = null
|
||||
moveHandler = (event = lastMoveEvent) =>
|
||||
@ -841,6 +854,7 @@ class Editor extends View
|
||||
@underlayer.css('top', -scrollTop)
|
||||
@overlayer.css('top', -scrollTop)
|
||||
@gutter.lineNumbers.css('top', -scrollTop)
|
||||
|
||||
if options?.adjustVerticalScrollbar ? true
|
||||
@verticalScrollbar.scrollTop(scrollTop)
|
||||
@activeEditSession.setScrollTop(@scrollTop())
|
||||
@ -961,6 +975,9 @@ class Editor extends View
|
||||
# fontSize - A {Number} indicating the font size in pixels.
|
||||
setFontSize: (fontSize) ->
|
||||
@css('font-size', "#{fontSize}px}")
|
||||
|
||||
@clearCharacterWidthCache()
|
||||
|
||||
if @isOnDom()
|
||||
@redraw()
|
||||
else
|
||||
@ -977,6 +994,9 @@ class Editor extends View
|
||||
# fontFamily - A {String} identifying the CSS `font-family`,
|
||||
setFontFamily: (fontFamily='') ->
|
||||
@css('font-family', fontFamily)
|
||||
|
||||
@clearCharacterWidthCache()
|
||||
|
||||
@redraw()
|
||||
|
||||
# Gets the font family for the editor.
|
||||
@ -1133,6 +1153,14 @@ class Editor extends View
|
||||
@layerMinWidth = minWidth
|
||||
@trigger 'editor:min-width-changed'
|
||||
|
||||
# Override for speed. The base function checks computedStyle, unnecessary here.
|
||||
isHidden: ->
|
||||
style = this[0].style
|
||||
if style.display == 'none' or not @isOnDom()
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
clearRenderedLines: ->
|
||||
@renderedLines.empty()
|
||||
@firstRenderedScreenRow = null
|
||||
@ -1184,9 +1212,15 @@ class Editor extends View
|
||||
for cursorView in @getCursorViews()
|
||||
if cursorView.needsRemoval
|
||||
cursorView.remove()
|
||||
else if cursorView.needsUpdate
|
||||
else if @shouldUpdateCursor(cursorView)
|
||||
cursorView.updateDisplay()
|
||||
|
||||
shouldUpdateCursor: (cursorView) ->
|
||||
return false unless cursorView.needsUpdate
|
||||
|
||||
pos = cursorView.getScreenPosition()
|
||||
pos.row >= @firstRenderedScreenRow and pos.row <= @lastRenderedScreenRow
|
||||
|
||||
updateSelectionViews: ->
|
||||
if @newSelections.length > 0
|
||||
@addSelectionView(selection) for selection in @newSelections when not selection.destroyed
|
||||
@ -1195,9 +1229,17 @@ class Editor extends View
|
||||
for selectionView in @getSelectionViews()
|
||||
if selectionView.needsRemoval
|
||||
selectionView.remove()
|
||||
else
|
||||
else if @shouldUpdateSelection(selectionView)
|
||||
selectionView.updateDisplay()
|
||||
|
||||
shouldUpdateSelection: (selectionView) ->
|
||||
screenRange = selectionView.getScreenRange()
|
||||
startRow = screenRange.start.row
|
||||
endRow = screenRange.end.row
|
||||
(startRow >= @firstRenderedScreenRow and startRow <= @lastRenderedScreenRow) or # startRow in range
|
||||
(endRow >= @firstRenderedScreenRow and endRow <= @lastRenderedScreenRow) or # endRow in range
|
||||
(startRow <= @firstRenderedScreenRow and endRow >= @lastRenderedScreenRow) # selection surrounds the rendered items
|
||||
|
||||
syncCursorAnimations: ->
|
||||
for cursorView in @getCursorViews()
|
||||
do (cursorView) -> cursorView.resetBlinking()
|
||||
@ -1229,10 +1271,11 @@ class Editor extends View
|
||||
if @pendingChanges.length == 0 and @firstRenderedScreenRow and @firstRenderedScreenRow <= renderFrom and renderTo <= @lastRenderedScreenRow
|
||||
return
|
||||
|
||||
@gutter.updateLineNumbers(@pendingChanges, renderFrom, renderTo)
|
||||
intactRanges = @computeIntactRanges()
|
||||
@pendingChanges = []
|
||||
@truncateIntactRanges(intactRanges, renderFrom, renderTo)
|
||||
changes = @pendingChanges
|
||||
intactRanges = @computeIntactRanges(renderFrom, renderTo)
|
||||
|
||||
@gutter.updateLineNumbers(changes, renderFrom, renderTo)
|
||||
|
||||
@clearDirtyRanges(intactRanges)
|
||||
@fillDirtyRanges(intactRanges, renderFrom, renderTo)
|
||||
@firstRenderedScreenRow = renderFrom
|
||||
@ -1258,7 +1301,7 @@ class Editor extends View
|
||||
|
||||
emptyLineChanges
|
||||
|
||||
computeIntactRanges: ->
|
||||
computeIntactRanges: (renderFrom, renderTo) ->
|
||||
return [] if !@firstRenderedScreenRow? and !@lastRenderedScreenRow?
|
||||
|
||||
intactRanges = [{start: @firstRenderedScreenRow, end: @lastRenderedScreenRow, domStart: 0}]
|
||||
@ -1293,6 +1336,9 @@ class Editor extends View
|
||||
domStart: range.domStart + change.end + 1 - range.start
|
||||
)
|
||||
intactRanges = newIntactRanges
|
||||
|
||||
@truncateIntactRanges(intactRanges, renderFrom, renderTo)
|
||||
|
||||
@pendingChanges = []
|
||||
|
||||
intactRanges
|
||||
@ -1312,35 +1358,35 @@ class Editor extends View
|
||||
intactRanges.sort (a, b) -> a.domStart - b.domStart
|
||||
|
||||
clearDirtyRanges: (intactRanges) ->
|
||||
renderedLines = @renderedLines[0]
|
||||
killLine = (line) ->
|
||||
next = line.nextSibling
|
||||
renderedLines.removeChild(line)
|
||||
next
|
||||
|
||||
if intactRanges.length == 0
|
||||
@renderedLines.empty()
|
||||
else if currentLine = renderedLines.firstChild
|
||||
@renderedLines[0].innerHTML = ''
|
||||
else if currentLine = @renderedLines[0].firstChild
|
||||
domPosition = 0
|
||||
for intactRange in intactRanges
|
||||
while intactRange.domStart > domPosition
|
||||
currentLine = killLine(currentLine)
|
||||
currentLine = @clearLine(currentLine)
|
||||
domPosition++
|
||||
for i in [intactRange.start..intactRange.end]
|
||||
currentLine = currentLine.nextSibling
|
||||
domPosition++
|
||||
while currentLine
|
||||
currentLine = killLine(currentLine)
|
||||
currentLine = @clearLine(currentLine)
|
||||
|
||||
clearLine: (lineElement) ->
|
||||
next = lineElement.nextSibling
|
||||
@renderedLines[0].removeChild(lineElement)
|
||||
next
|
||||
|
||||
fillDirtyRanges: (intactRanges, renderFrom, renderTo) ->
|
||||
renderedLines = @renderedLines[0]
|
||||
nextIntact = intactRanges.shift()
|
||||
currentLine = renderedLines.firstChild
|
||||
i = 0
|
||||
nextIntact = intactRanges[i]
|
||||
currentLine = @renderedLines[0].firstChild
|
||||
|
||||
row = renderFrom
|
||||
while row <= renderTo
|
||||
if row == nextIntact?.end + 1
|
||||
nextIntact = intactRanges.shift()
|
||||
nextIntact = intactRanges[++i]
|
||||
|
||||
if !nextIntact or row < nextIntact.start
|
||||
if nextIntact
|
||||
dirtyRangeEnd = nextIntact.start - 1
|
||||
@ -1348,7 +1394,7 @@ class Editor extends View
|
||||
dirtyRangeEnd = renderTo
|
||||
|
||||
for lineElement in @buildLineElementsForScreenRows(row, dirtyRangeEnd)
|
||||
renderedLines.insertBefore(lineElement, currentLine)
|
||||
@renderedLines[0].insertBefore(lineElement, currentLine)
|
||||
row++
|
||||
else
|
||||
currentLine = currentLine.nextSibling
|
||||
@ -1369,14 +1415,18 @@ class Editor extends View
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getFirstVisibleScreenRow: ->
|
||||
Math.floor(@scrollTop() / @lineHeight)
|
||||
screenRow = Math.floor(@scrollTop() / @lineHeight)
|
||||
screenRow = 0 if isNaN(screenRow)
|
||||
screenRow
|
||||
|
||||
# Retrieves the number of the row that is visible and currently at the bottom of the editor.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getLastVisibleScreenRow: ->
|
||||
calculatedRow = Math.ceil((@scrollTop() + @scrollView.height()) / @lineHeight) - 1
|
||||
Math.max(0, Math.min(@getScreenLineCount() - 1, calculatedRow))
|
||||
screenRow = Math.max(0, Math.min(@getScreenLineCount() - 1, calculatedRow))
|
||||
screenRow = 0 if isNaN(screenRow)
|
||||
screenRow
|
||||
|
||||
# Given a row number, identifies if it is currently visible.
|
||||
#
|
||||
@ -1401,11 +1451,11 @@ class Editor extends View
|
||||
new Array(div.children...)
|
||||
|
||||
htmlForScreenRows: (startRow, endRow) ->
|
||||
htmlLines = []
|
||||
htmlLines = ''
|
||||
screenRow = startRow
|
||||
for line in @activeEditSession.linesForScreenRows(startRow, endRow)
|
||||
htmlLines.push(@htmlForScreenLine(line, screenRow++))
|
||||
htmlLines.join('\n\n')
|
||||
htmlLines += @htmlForScreenLine(line, screenRow++)
|
||||
htmlLines
|
||||
|
||||
htmlForScreenLine: (screenLine, screenRow) ->
|
||||
{ tokens, text, lineEnding, fold, isSoftWrapped } = screenLine
|
||||
@ -1496,28 +1546,92 @@ class Editor extends View
|
||||
unless existingLineElement
|
||||
lineElement = @buildLineElementForScreenRow(actualRow)
|
||||
@renderedLines.append(lineElement)
|
||||
left = @positionLeftForLineAndColumn(lineElement, column)
|
||||
left = @positionLeftForLineAndColumn(lineElement, actualRow, column)
|
||||
unless existingLineElement
|
||||
@renderedLines[0].removeChild(lineElement)
|
||||
{ top: row * @lineHeight, left }
|
||||
|
||||
positionLeftForLineAndColumn: (lineElement, column) ->
|
||||
return 0 if column is 0
|
||||
delta = 0
|
||||
iterator = document.createNodeIterator(lineElement, NodeFilter.SHOW_TEXT, acceptNode: -> NodeFilter.FILTER_ACCEPT)
|
||||
while textNode = iterator.nextNode()
|
||||
nextDelta = delta + textNode.textContent.length
|
||||
if nextDelta >= column
|
||||
offset = column - delta
|
||||
break
|
||||
delta = nextDelta
|
||||
positionLeftForLineAndColumn: (lineElement, screenRow, column) ->
|
||||
return 0 if column == 0
|
||||
|
||||
range = document.createRange()
|
||||
range.setEnd(textNode, offset)
|
||||
range.collapse()
|
||||
leftPixels = range.getClientRects()[0].left - Math.floor(@scrollView.offset().left) + Math.floor(@scrollLeft())
|
||||
range.detach()
|
||||
leftPixels
|
||||
bufferRow = @bufferRowsForScreenRows(screenRow, screenRow)[0] ? screenRow
|
||||
tokenizedLine = @activeEditSession.displayBuffer.tokenizedBuffer.tokenizedLines[bufferRow]
|
||||
|
||||
left = 0
|
||||
index = 0
|
||||
for token in tokenizedLine.tokens
|
||||
for char in token.value
|
||||
return left if index >= column
|
||||
|
||||
val = @getCharacterWidthCache(token.scopes, char)
|
||||
if val?
|
||||
left += val
|
||||
else
|
||||
return @measureToColumn(lineElement, tokenizedLine, column)
|
||||
|
||||
index++
|
||||
left
|
||||
|
||||
scopesForColumn: (tokenizedLine, column) ->
|
||||
index = 0
|
||||
for token in tokenizedLine.tokens
|
||||
for char in token.value
|
||||
return token.scopes if index == column
|
||||
index++
|
||||
null
|
||||
|
||||
measureToColumn: (lineElement, tokenizedLine, column) ->
|
||||
left = oldLeft = index = 0
|
||||
iterator = document.createNodeIterator(lineElement, NodeFilter.SHOW_TEXT, TextNodeFilter)
|
||||
|
||||
returnLeft = null
|
||||
|
||||
while textNode = iterator.nextNode()
|
||||
content = textNode.textContent
|
||||
|
||||
for char, i in content
|
||||
|
||||
# Dont return right away, finish caching the whole line
|
||||
returnLeft = left if index == column
|
||||
oldLeft = left
|
||||
|
||||
scopes = @scopesForColumn(tokenizedLine, index)
|
||||
cachedVal = @getCharacterWidthCache(scopes, char)
|
||||
|
||||
if cachedVal?
|
||||
left = oldLeft + cachedVal
|
||||
else
|
||||
# i + 1 to measure to the end of the current character
|
||||
MeasureRange.setEnd(textNode, i + 1)
|
||||
MeasureRange.collapse()
|
||||
rects = MeasureRange.getClientRects()
|
||||
return 0 if rects.length == 0
|
||||
left = rects[0].left - Math.floor(@scrollView.offset().left) + Math.floor(@scrollLeft())
|
||||
|
||||
@setCharacterWidthCache(scopes, char, left - oldLeft) if scopes?
|
||||
|
||||
index++
|
||||
|
||||
returnLeft ? left
|
||||
|
||||
getCharacterWidthCache: (scopes, char) ->
|
||||
scopes ?= NoScope
|
||||
obj = Editor.characterWidthCache
|
||||
for scope in scopes
|
||||
obj = obj[scope]
|
||||
return null unless obj?
|
||||
obj[char]
|
||||
|
||||
setCharacterWidthCache: (scopes, char, val) ->
|
||||
scopes ?= NoScope
|
||||
obj = Editor.characterWidthCache
|
||||
for scope in scopes
|
||||
obj[scope] ?= {}
|
||||
obj = obj[scope]
|
||||
obj[char] = val
|
||||
|
||||
clearCharacterWidthCache: ->
|
||||
Editor.characterWidthCache = {}
|
||||
|
||||
pixelOffsetForScreenPosition: (position) ->
|
||||
{top, left} = @pixelPositionForScreenPosition(position)
|
||||
@ -1591,30 +1705,9 @@ class Editor extends View
|
||||
scopeStack = []
|
||||
line = []
|
||||
|
||||
updateScopeStack = (desiredScopes) ->
|
||||
excessScopes = scopeStack.length - desiredScopes.length
|
||||
_.times(excessScopes, popScope) if excessScopes > 0
|
||||
|
||||
# pop until common prefix
|
||||
for i in [scopeStack.length..0]
|
||||
break if _.isEqual(scopeStack[0...i], desiredScopes[0...i])
|
||||
popScope()
|
||||
|
||||
# push on top of common prefix until scopeStack == desiredScopes
|
||||
for j in [i...desiredScopes.length]
|
||||
pushScope(desiredScopes[j])
|
||||
|
||||
pushScope = (scope) ->
|
||||
scopeStack.push(scope)
|
||||
line.push("<span class=\"#{scope.replace(/\./g, ' ')}\">")
|
||||
|
||||
popScope = ->
|
||||
scopeStack.pop()
|
||||
line.push("</span>")
|
||||
|
||||
attributePairs = []
|
||||
attributePairs.push "#{attributeName}=\"#{value}\"" for attributeName, value of attributes
|
||||
line.push("<div #{attributePairs.join(' ')}>")
|
||||
attributePairs = ''
|
||||
attributePairs += " #{attributeName}=\"#{value}\"" for attributeName, value of attributes
|
||||
line.push("<div #{attributePairs}>")
|
||||
|
||||
if text == ''
|
||||
html = Editor.buildEmptyLineHtml(showIndentGuide, eolInvisibles, htmlEolInvisibles, indentation, activeEditSession, mini)
|
||||
@ -1625,39 +1718,64 @@ class Editor extends View
|
||||
lineIsWhitespaceOnly = firstTrailingWhitespacePosition is 0
|
||||
position = 0
|
||||
for token in tokens
|
||||
updateScopeStack(token.scopes)
|
||||
@updateScopeStack(line, scopeStack, token.scopes)
|
||||
hasLeadingWhitespace = position < firstNonWhitespacePosition
|
||||
hasTrailingWhitespace = position + token.value.length > firstTrailingWhitespacePosition
|
||||
hasIndentGuide = not mini and showIndentGuide and (hasLeadingWhitespace or lineIsWhitespaceOnly)
|
||||
line.push(token.getValueAsHtml({invisibles, hasLeadingWhitespace, hasTrailingWhitespace, hasIndentGuide}))
|
||||
position += token.value.length
|
||||
|
||||
popScope() while scopeStack.length > 0
|
||||
@popScope(line, scopeStack) while scopeStack.length > 0
|
||||
line.push(htmlEolInvisibles) unless text == ''
|
||||
line.push("<span class='fold-marker'/>") if fold
|
||||
|
||||
line.push('</div>')
|
||||
line.join('')
|
||||
|
||||
@updateScopeStack: (line, scopeStack, desiredScopes) ->
|
||||
excessScopes = scopeStack.length - desiredScopes.length
|
||||
if excessScopes > 0
|
||||
@popScope(line, scopeStack) while excessScopes--
|
||||
|
||||
# pop until common prefix
|
||||
for i in [scopeStack.length..0]
|
||||
break if _.isEqual(scopeStack[0...i], desiredScopes[0...i])
|
||||
@popScope(line, scopeStack)
|
||||
|
||||
# push on top of common prefix until scopeStack == desiredScopes
|
||||
for j in [i...desiredScopes.length]
|
||||
@pushScope(line, scopeStack, desiredScopes[j])
|
||||
|
||||
null
|
||||
|
||||
@pushScope: (line, scopeStack, scope) ->
|
||||
scopeStack.push(scope)
|
||||
line.push("<span class=\"#{scope.replace(/\./g, ' ')}\">")
|
||||
|
||||
@popScope: (line, scopeStack) ->
|
||||
scopeStack.pop()
|
||||
line.push("</span>")
|
||||
|
||||
@buildEmptyLineHtml: (showIndentGuide, eolInvisibles, htmlEolInvisibles, indentation, activeEditSession, mini) ->
|
||||
indentCharIndex = 0
|
||||
if not mini and showIndentGuide
|
||||
if indentation > 0
|
||||
tabLength = activeEditSession.getTabLength()
|
||||
indentGuideHtml = []
|
||||
indentGuideHtml = ''
|
||||
for level in [0...indentation]
|
||||
indentLevelHtml = ["<span class='indent-guide'>"]
|
||||
indentLevelHtml = "<span class='indent-guide'>"
|
||||
for characterPosition in [0...tabLength]
|
||||
if invisible = eolInvisibles.shift()
|
||||
indentLevelHtml.push("<span class='invisible-character'>#{invisible}</span>")
|
||||
if invisible = eolInvisibles[indentCharIndex++]
|
||||
indentLevelHtml += "<span class='invisible-character'>#{invisible}</span>"
|
||||
else
|
||||
indentLevelHtml.push(' ')
|
||||
indentLevelHtml.push("</span>")
|
||||
indentGuideHtml.push(indentLevelHtml.join(''))
|
||||
indentLevelHtml += ' '
|
||||
indentLevelHtml += "</span>"
|
||||
indentGuideHtml += indentLevelHtml
|
||||
|
||||
for invisible in eolInvisibles
|
||||
indentGuideHtml.push("<span class='invisible-character'>#{invisible}</span>")
|
||||
indentGuideHtml += "<span class='invisible-character'>#{invisible}</span>"
|
||||
|
||||
return indentGuideHtml.join('')
|
||||
return indentGuideHtml
|
||||
|
||||
if htmlEolInvisibles.length > 0
|
||||
htmlEolInvisibles
|
||||
|
@ -44,6 +44,7 @@ class Git
|
||||
path: null
|
||||
statuses: null
|
||||
upstream: null
|
||||
branch: null
|
||||
statusTask: null
|
||||
|
||||
# Private: Creates a new `Git` object.
|
||||
@ -142,6 +143,12 @@ class Git
|
||||
# Public: Determine if the given path is new.
|
||||
isPathNew: (path) -> @isStatusNew(@getPathStatus(path))
|
||||
|
||||
# Public: Is the project at the root of this repository?
|
||||
#
|
||||
# Returns true if at the root, false if in a subfolder of the repository.
|
||||
isProjectAtRoot: ->
|
||||
@projectAtRoot ?= project.relativize(@getWorkingDirectory()) is ''
|
||||
|
||||
# Public: Makes a path relative to the repository's working directory.
|
||||
relativize: (path) -> @getRepo().relativize(path)
|
||||
|
||||
@ -171,6 +178,15 @@ class Git
|
||||
@getPathStatus(path) if headCheckedOut
|
||||
headCheckedOut
|
||||
|
||||
# Public: Checks out a branch in your repository.
|
||||
#
|
||||
# reference - The {String} reference to checkout
|
||||
# create - A {Boolean} value which, if `true` creates the new reference if it doesn't exist.
|
||||
#
|
||||
# Returns a {Boolean} that's `true` if the method was successful.
|
||||
checkoutReference: (reference, create) ->
|
||||
@getRepo().checkoutReference(reference, create)
|
||||
|
||||
# Public: Retrieves the number of lines added and removed to a path.
|
||||
#
|
||||
# This compares the working directory contents of the path to the `HEAD`
|
||||
@ -239,6 +255,12 @@ class Git
|
||||
# Public: ?
|
||||
getReferenceTarget: (reference) -> @getRepo().getReferenceTarget(reference)
|
||||
|
||||
# Public: Gets all the local and remote references.
|
||||
#
|
||||
# Returns an object with three keys: `heads`, `remotes`, and `tags`. Each key
|
||||
# can be an array of strings containing the reference names.
|
||||
getReferences: -> @getRepo().getReferences()
|
||||
|
||||
# Public: ?
|
||||
getAheadBehindCount: (reference) -> @getRepo().getAheadBehindCount(reference)
|
||||
|
||||
@ -247,8 +269,9 @@ class Git
|
||||
|
||||
# Private:
|
||||
refreshStatus: ->
|
||||
@statusTask = Task.once require.resolve('./repository-status-handler'), @getPath(), ({statuses, upstream}) =>
|
||||
statusesUnchanged = _.isEqual(statuses, @statuses) and _.isEqual(upstream, @upstream)
|
||||
@statusTask = Task.once require.resolve('./repository-status-handler'), @getPath(), ({statuses, upstream, branch}) =>
|
||||
statusesUnchanged = _.isEqual(statuses, @statuses) and _.isEqual(upstream, @upstream) and _.isEqual(branch, @branch)
|
||||
@statuses = statuses
|
||||
@upstream = upstream
|
||||
@branch = branch
|
||||
@trigger 'statuses-changed' unless statusesUnchanged
|
||||
|
@ -15,8 +15,11 @@ class Gutter extends View
|
||||
@div class: 'gutter', =>
|
||||
@div outlet: 'lineNumbers', class: 'line-numbers'
|
||||
|
||||
firstScreenRow: Infinity
|
||||
lastScreenRow: -1
|
||||
firstScreenRow: null
|
||||
lastScreenRow: null
|
||||
|
||||
initialize: ->
|
||||
@elementBuilder = document.createElement('div')
|
||||
|
||||
afterAttach: (onDom) ->
|
||||
return if @attached or not onDom
|
||||
@ -62,46 +65,157 @@ class Gutter extends View
|
||||
setShowLineNumbers: (showLineNumbers) ->
|
||||
if showLineNumbers then @lineNumbers.show() else @lineNumbers.hide()
|
||||
|
||||
# Get all the line-number divs.
|
||||
#
|
||||
# Returns a list of {HTMLElement}s.
|
||||
getLineNumberElements: ->
|
||||
@lineNumbers[0].children
|
||||
|
||||
# Get all the line-number divs.
|
||||
#
|
||||
# Returns a list of {HTMLElement}s.
|
||||
getLineNumberElementsForClass: (klass) ->
|
||||
@lineNumbers[0].getElementsByClassName(klass)
|
||||
|
||||
# Get a single line-number div.
|
||||
#
|
||||
# * bufferRow: 0 based line number
|
||||
#
|
||||
# Returns a list of {HTMLElement}s that correspond to the bufferRow. More than
|
||||
# one in the list indicates a wrapped line.
|
||||
getLineNumberElement: (bufferRow) ->
|
||||
@getLineNumberElementsForClass("line-number-#{bufferRow}")
|
||||
|
||||
# Add a class to all line-number divs.
|
||||
#
|
||||
# * klass: string class name
|
||||
#
|
||||
# Returns true if the class was added to any lines
|
||||
addClassToAllLines: (klass)->
|
||||
elements = @getLineNumberElements()
|
||||
el.classList.add(klass) for el in elements
|
||||
!!elements.length
|
||||
|
||||
# Remove a class from all line-number divs.
|
||||
#
|
||||
# * klass: string class name. Can only be one class name. i.e. 'my-class'
|
||||
#
|
||||
# Returns true if the class was removed from any lines
|
||||
removeClassFromAllLines: (klass)->
|
||||
# This is faster than calling $.removeClass on all lines, and faster than
|
||||
# making a new array and iterating through it.
|
||||
elements = @getLineNumberElementsForClass(klass)
|
||||
willRemoveClasses = !!elements.length
|
||||
elements[0].classList.remove(klass) while elements.length > 0
|
||||
willRemoveClasses
|
||||
|
||||
# Add a class to a single line-number div
|
||||
#
|
||||
# * bufferRow: 0 based line number
|
||||
# * klass: string class name
|
||||
#
|
||||
# Returns true if there were lines the class was added to
|
||||
addClassToLine: (bufferRow, klass)->
|
||||
elements = @getLineNumberElement(bufferRow)
|
||||
el.classList.add(klass) for el in elements
|
||||
!!elements.length
|
||||
|
||||
# Remove a class from a single line-number div
|
||||
#
|
||||
# * bufferRow: 0 based line number
|
||||
# * klass: string class name
|
||||
#
|
||||
# Returns true if there were lines the class was removed from
|
||||
removeClassFromLine: (bufferRow, klass)->
|
||||
classesRemoved = false
|
||||
elements = @getLineNumberElement(bufferRow)
|
||||
for el in elements
|
||||
hasClass = el.classList.contains(klass)
|
||||
classesRemoved |= hasClass
|
||||
el.classList.remove(klass) if hasClass
|
||||
classesRemoved
|
||||
|
||||
### Internal ###
|
||||
|
||||
updateLineNumbers: (changes, renderFrom, renderTo) ->
|
||||
if renderFrom < @firstScreenRow or renderTo > @lastScreenRow
|
||||
performUpdate = true
|
||||
else if @getEditor().getLastScreenRow() < @lastScreenRow
|
||||
performUpdate = true
|
||||
updateLineNumbers: (changes, startScreenRow, endScreenRow) ->
|
||||
# Check if we have something already rendered that overlaps the requested range
|
||||
updateAllLines = not (startScreenRow? and endScreenRow?)
|
||||
updateAllLines |= endScreenRow <= @firstScreenRow or startScreenRow >= @lastScreenRow
|
||||
|
||||
for change in changes
|
||||
# When there is a change to the bufferRow -> screenRow map (i.e. a fold),
|
||||
# then rerender everything.
|
||||
if (change.screenDelta or change.bufferDelta) and change.screenDelta != change.bufferDelta
|
||||
updateAllLines = true
|
||||
break
|
||||
|
||||
if updateAllLines
|
||||
@lineNumbers[0].innerHTML = @buildLineElementsHtml(startScreenRow, endScreenRow)
|
||||
else
|
||||
for change in changes
|
||||
if change.screenDelta or change.bufferDelta
|
||||
performUpdate = true
|
||||
break
|
||||
# When scrolling or adding/removing lines, we just add/remove lines from the ends.
|
||||
if startScreenRow < @firstScreenRow
|
||||
@prependLineElements(@buildLineElements(startScreenRow, @firstScreenRow-1))
|
||||
else if startScreenRow != @firstScreenRow
|
||||
@removeLineElements(startScreenRow - @firstScreenRow)
|
||||
|
||||
@renderLineNumbers(renderFrom, renderTo) if performUpdate
|
||||
|
||||
renderLineNumbers: (startScreenRow, endScreenRow) ->
|
||||
editor = @getEditor()
|
||||
maxDigits = editor.getLineCount().toString().length
|
||||
rows = editor.bufferRowsForScreenRows(startScreenRow, endScreenRow)
|
||||
|
||||
cursorScreenRow = editor.getCursorScreenPosition().row
|
||||
@lineNumbers[0].innerHTML = $$$ ->
|
||||
for row in rows
|
||||
if row == lastScreenRow
|
||||
rowValue = '•'
|
||||
else
|
||||
rowValue = (row + 1).toString()
|
||||
classes = ['line-number']
|
||||
classes.push('fold') if editor.isFoldedAtBufferRow(row)
|
||||
@div linenumber: row, class: classes.join(' '), =>
|
||||
rowValuePadding = _.multiplyString(' ', maxDigits - rowValue.length)
|
||||
@raw("#{rowValuePadding}#{rowValue}")
|
||||
|
||||
lastScreenRow = row
|
||||
if endScreenRow > @lastScreenRow
|
||||
@appendLineElements(@buildLineElements(@lastScreenRow+1, endScreenRow))
|
||||
else if endScreenRow != @lastScreenRow
|
||||
@removeLineElements(endScreenRow - @lastScreenRow)
|
||||
|
||||
@firstScreenRow = startScreenRow
|
||||
@lastScreenRow = endScreenRow
|
||||
@highlightedRows = null
|
||||
@highlightLines()
|
||||
|
||||
prependLineElements: (lineElements) ->
|
||||
anchor = @lineNumbers[0].children[0]
|
||||
return appendLineElements(lineElements) unless anchor?
|
||||
@lineNumbers[0].insertBefore(lineElements[0], anchor) while lineElements.length > 0
|
||||
null # defeat coffeescript array return
|
||||
|
||||
appendLineElements: (lineElements) ->
|
||||
@lineNumbers[0].appendChild(lineElements[0]) while lineElements.length > 0
|
||||
null # defeat coffeescript array return
|
||||
|
||||
removeLineElements: (numberOfElements) ->
|
||||
children = @getLineNumberElements()
|
||||
|
||||
# children is a live NodeList, so remove from the desired end {numberOfElements} times
|
||||
if numberOfElements < 0
|
||||
@lineNumbers[0].removeChild(children[children.length-1]) while numberOfElements++
|
||||
else if numberOfElements > 0
|
||||
@lineNumbers[0].removeChild(children[0]) while numberOfElements--
|
||||
|
||||
null # defeat coffeescript array return
|
||||
|
||||
buildLineElements: (startScreenRow, endScreenRow) ->
|
||||
@elementBuilder.innerHTML = @buildLineElementsHtml(startScreenRow, endScreenRow)
|
||||
@elementBuilder.children
|
||||
|
||||
buildLineElementsHtml: (startScreenRow, endScreenRow) =>
|
||||
editor = @getEditor()
|
||||
maxDigits = editor.getLineCount().toString().length
|
||||
rows = editor.bufferRowsForScreenRows(startScreenRow, endScreenRow)
|
||||
|
||||
html = ''
|
||||
for row in rows
|
||||
if row == lastScreenRow
|
||||
rowValue = '•'
|
||||
else
|
||||
rowValue = (row + 1).toString()
|
||||
|
||||
classes = "line-number line-number-#{row}"
|
||||
classes += ' fold' if editor.isFoldedAtBufferRow(row)
|
||||
|
||||
rowValuePadding = _.multiplyString(' ', maxDigits - rowValue.length)
|
||||
|
||||
html += """<div class="#{classes}">#{rowValuePadding}#{rowValue}</div>"""
|
||||
|
||||
lastScreenRow = row
|
||||
|
||||
html
|
||||
|
||||
removeLineHighlights: ->
|
||||
return unless @highlightedLineNumbers
|
||||
for line in @highlightedLineNumbers
|
||||
|
@ -38,18 +38,16 @@ $.fn.isVisible = ->
|
||||
!@isHidden()
|
||||
|
||||
$.fn.isHidden = ->
|
||||
# Implementation taken from jQuery's `:hidden` expression code:
|
||||
# https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js
|
||||
#
|
||||
# We were using a pseudo selector: @is(':hidden'). But jQuery's pseudo
|
||||
# selector code checks the element's webkitMatchesSelector, which is always
|
||||
# false, and is really really really slow.
|
||||
# We used to check @is(':hidden'). But this is much faster than the
|
||||
# offsetWidth/offsetHeight check + all the pseudo selector mess in jquery.
|
||||
style = this[0].style
|
||||
|
||||
elem = this[0]
|
||||
|
||||
return null unless elem
|
||||
|
||||
elem.offsetWidth <= 0 and elem.offsetHeight <= 0
|
||||
if style.display == 'none' or not @isOnDom()
|
||||
true
|
||||
else if style.display
|
||||
false
|
||||
else
|
||||
getComputedStyle(this[0]).display == 'none'
|
||||
|
||||
$.fn.isDisabled = ->
|
||||
!!@attr('disabled')
|
||||
|
@ -46,7 +46,7 @@ delegate.browserMainParts.preMainMessageLoopRun = ->
|
||||
|
||||
require('coffee-script')
|
||||
if args.devMode
|
||||
require(path.join(args.resourcePath, 'src', 'coffee-cache'))
|
||||
require(path.join(args.resourcePath, 'src', 'coffee-cache')).register()
|
||||
module.globalPaths.push(path.join(args.resourcePath, 'src'))
|
||||
else
|
||||
appSrcPath = path.resolve(process.argv[0], "../../Resources/app/src")
|
||||
|
53
src/menu-manager.coffee
Normal file
53
src/menu-manager.coffee
Normal file
@ -0,0 +1,53 @@
|
||||
path = require 'path'
|
||||
|
||||
_ = require 'underscore'
|
||||
ipc = require 'ipc'
|
||||
CSON = require 'season'
|
||||
|
||||
fsUtils = require './fs-utils'
|
||||
|
||||
# Public: Provides a registry for menu items that you'd like to appear in the
|
||||
# application menu.
|
||||
#
|
||||
# Should be accessed via `atom.menu`.
|
||||
module.exports =
|
||||
class MenuManager
|
||||
# Private:
|
||||
constructor: ->
|
||||
@template = []
|
||||
@loadCoreItems()
|
||||
|
||||
# Public: Adds the given item definition to the existing template.
|
||||
#
|
||||
# * item:
|
||||
# An object which describes a menu item as defined by
|
||||
# https://github.com/atom/atom-shell/blob/master/docs/api/browser/menu.md
|
||||
#
|
||||
# Returns nothing.
|
||||
add: (items) ->
|
||||
@merge(@template, item) for item in items
|
||||
@update()
|
||||
|
||||
# Public: Refreshes the currently visible menu.
|
||||
update: ->
|
||||
@sendToBrowserProcess(@template, atom.keymap.keystrokesByCommandForSelector('body'))
|
||||
|
||||
# Private
|
||||
loadCoreItems: ->
|
||||
menuPaths = fsUtils.listSync(atom.config.bundledMenusDirPath, ['cson', 'json'])
|
||||
for menuPath in menuPaths
|
||||
data = CSON.readFileSync(menuPath)
|
||||
@add(data.menu)
|
||||
|
||||
# Private: Merges an item in a submenu aware way such that new items are always
|
||||
# appended to the bottom of existing menus where possible.
|
||||
merge: (menu, item) ->
|
||||
if item.submenu? and match = _.find(menu, (o) -> o.submenu? and o.label == item.label)
|
||||
@merge(match.submenu, i) for i in item.submenu
|
||||
else
|
||||
menu.push(item)
|
||||
|
||||
# Private
|
||||
sendToBrowserProcess: (template, keystrokesByCommand) ->
|
||||
ipc.sendChannel 'update-application-menu', template, keystrokesByCommand
|
||||
|
@ -8,7 +8,11 @@ module.exports =
|
||||
class PackageManager
|
||||
_.extend @prototype, EventEmitter
|
||||
|
||||
constructor: ->
|
||||
constructor: ({configDirPath, devMode, @resourcePath}) ->
|
||||
@packageDirPaths = [path.join(configDirPath, "packages")]
|
||||
if devMode
|
||||
@packageDirPaths.unshift(path.join(configDirPath, "dev", "packages"))
|
||||
|
||||
@loadedPackages = {}
|
||||
@activePackages = {}
|
||||
@packageStates = {}
|
||||
@ -83,10 +87,10 @@ class PackageManager
|
||||
resolvePackagePath: (name) ->
|
||||
return name if fsUtils.isDirectorySync(name)
|
||||
|
||||
packagePath = fsUtils.resolve(config.packageDirPaths..., name)
|
||||
packagePath = fsUtils.resolve(@packageDirPaths..., name)
|
||||
return packagePath if fsUtils.isDirectorySync(packagePath)
|
||||
|
||||
packagePath = path.join(window.resourcePath, 'node_modules', name)
|
||||
packagePath = path.join(@resourcePath, 'node_modules', name)
|
||||
return packagePath if @isInternalPackage(packagePath)
|
||||
|
||||
isInternalPackage: (packagePath) ->
|
||||
@ -108,11 +112,11 @@ class PackageManager
|
||||
getAvailablePackagePaths: ->
|
||||
packagePaths = []
|
||||
|
||||
for packageDirPath in config.packageDirPaths
|
||||
for packageDirPath in @packageDirPaths
|
||||
for packagePath in fsUtils.listSync(packageDirPath)
|
||||
packagePaths.push(packagePath) if fsUtils.isDirectorySync(packagePath)
|
||||
|
||||
for packagePath in fsUtils.listSync(path.join(window.resourcePath, 'node_modules'))
|
||||
for packagePath in fsUtils.listSync(path.join(@resourcePath, 'node_modules'))
|
||||
packagePaths.push(packagePath) if @isInternalPackage(packagePath)
|
||||
|
||||
_.uniq(packagePaths)
|
||||
@ -122,8 +126,8 @@ class PackageManager
|
||||
|
||||
getAvailablePackageMetadata: ->
|
||||
packages = []
|
||||
for packagePath in atom.getAvailablePackagePaths()
|
||||
for packagePath in @getAvailablePackagePaths()
|
||||
name = path.basename(packagePath)
|
||||
metadata = atom.getLoadedPackage(name)?.metadata ? Package.loadMetadata(packagePath, true)
|
||||
metadata = @getLoadedPackage(name)?.metadata ? Package.loadMetadata(packagePath, true)
|
||||
packages.push(metadata)
|
||||
packages
|
||||
|
@ -9,9 +9,11 @@ module.exports = (repoPath) ->
|
||||
for filePath, status of repo.getStatus()
|
||||
statuses[path.join(workingDirectoryPath, filePath)] = status
|
||||
upstream = repo.getAheadBehindCount()
|
||||
branch = repo.getHead()
|
||||
repo.release()
|
||||
else
|
||||
upstream = {}
|
||||
statuses = {}
|
||||
branch = null
|
||||
|
||||
{statuses, upstream}
|
||||
{statuses, upstream, branch}
|
||||
|
@ -44,15 +44,11 @@ class Task
|
||||
# function to execute.
|
||||
constructor: (taskPath) ->
|
||||
coffeeScriptRequire = "require('#{require.resolve('coffee-script')}');"
|
||||
coffeeCacheRequire = "require('#{require.resolve('./coffee-cache')}');"
|
||||
coffeeCacheRequire = "require('#{require.resolve('./coffee-cache')}').register();"
|
||||
taskBootstrapRequire = "require('#{require.resolve('./task-bootstrap')}');"
|
||||
bootstrap = """
|
||||
#{coffeeScriptRequire}
|
||||
#{coffeeCacheRequire}
|
||||
Object.defineProperty(require.extensions, '.coffee', {
|
||||
writable: false,
|
||||
value: require.extensions['.coffee']
|
||||
});
|
||||
#{taskBootstrapRequire}
|
||||
"""
|
||||
|
||||
|
@ -1,7 +1,14 @@
|
||||
_ = require './underscore-extensions'
|
||||
textUtils = require './text-utils'
|
||||
|
||||
whitespaceRegexesByTabLength = {}
|
||||
WhitespaceRegexesByTabLength = {}
|
||||
LeadingWhitespaceRegex = /^[ ]+/
|
||||
TrailingWhitespaceRegex = /[ ]+$/
|
||||
EscapeRegex = /[&"'<>]/g
|
||||
CharacterRegex = /./g
|
||||
StartCharacterRegex = /^./
|
||||
StartDotRegex = /^\.?/
|
||||
WhitespaceRegex = /\S/
|
||||
|
||||
# Private: Represents a single unit of text as selected by a grammar.
|
||||
module.exports =
|
||||
@ -33,7 +40,7 @@ class Token
|
||||
[new Token(value: value1, scopes: @scopes), new Token(value: value2, scopes: @scopes)]
|
||||
|
||||
whitespaceRegexForTabLength: (tabLength) ->
|
||||
whitespaceRegexesByTabLength[tabLength] ?= new RegExp("([ ]{#{tabLength}})|(\t)|([^\t]+)", "g")
|
||||
WhitespaceRegexesByTabLength[tabLength] ?= new RegExp("([ ]{#{tabLength}})|(\t)|([^\t]+)", "g")
|
||||
|
||||
breakOutAtomicTokens: (tabLength, breakOutLeadingWhitespace) ->
|
||||
if @hasSurrogatePair
|
||||
@ -112,10 +119,10 @@ class Token
|
||||
)
|
||||
|
||||
isOnlyWhitespace: ->
|
||||
not /\S/.test(@value)
|
||||
not WhitespaceRegex.test(@value)
|
||||
|
||||
matchesScopeSelector: (selector) ->
|
||||
targetClasses = selector.replace(/^\.?/, '').split('.')
|
||||
targetClasses = selector.replace(StartDotRegex, '').split('.')
|
||||
_.any @scopes, (scope) ->
|
||||
scopeClasses = scope.split('.')
|
||||
_.isSubset(targetClasses, scopeClasses)
|
||||
@ -123,39 +130,59 @@ class Token
|
||||
getValueAsHtml: ({invisibles, hasLeadingWhitespace, hasTrailingWhitespace, hasIndentGuide})->
|
||||
invisibles ?= {}
|
||||
html = @value
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
|
||||
if @isHardTab
|
||||
classes = []
|
||||
classes.push('indent-guide') if hasIndentGuide
|
||||
classes.push('invisible-character') if invisibles.tab
|
||||
classes.push('hard-tab')
|
||||
classes = classes.join(' ')
|
||||
html = html.replace /^./, (match) ->
|
||||
classes = 'hard-tab'
|
||||
classes += ' indent-guide' if hasIndentGuide
|
||||
classes += ' invisible-character' if invisibles.tab
|
||||
html = html.replace StartCharacterRegex, (match) =>
|
||||
match = invisibles.tab ? match
|
||||
"<span class='#{classes}'>#{match}</span>"
|
||||
"<span class='#{classes}'>#{@escapeString(match)}</span>"
|
||||
else
|
||||
if hasLeadingWhitespace
|
||||
classes = []
|
||||
classes.push('indent-guide') if hasIndentGuide
|
||||
classes.push('invisible-character') if invisibles.space
|
||||
classes.push('leading-whitespace')
|
||||
classes = classes.join(' ')
|
||||
html = html.replace /^[ ]+/, (match) ->
|
||||
match = match.replace(/./g, invisibles.space) if invisibles.space
|
||||
"<span class='#{classes}'>#{match}</span>"
|
||||
if hasTrailingWhitespace
|
||||
classes = []
|
||||
classes.push('indent-guide') if hasIndentGuide and not hasLeadingWhitespace
|
||||
classes.push('invisible-character') if invisibles.space
|
||||
classes.push('trailing-whitespace')
|
||||
classes = classes.join(' ')
|
||||
html = html.replace /[ ]+$/, (match) ->
|
||||
match = match.replace(/./g, invisibles.space) if invisibles.space
|
||||
"<span class='#{classes}'>#{match}</span>"
|
||||
startIndex = 0
|
||||
endIndex = html.length
|
||||
|
||||
leadingHtml = ''
|
||||
trailingHtml = ''
|
||||
|
||||
if hasLeadingWhitespace and match = LeadingWhitespaceRegex.exec(html)
|
||||
classes = 'leading-whitespace'
|
||||
classes += ' indent-guide' if hasIndentGuide
|
||||
classes += ' invisible-character' if invisibles.space
|
||||
|
||||
match[0] = match[0].replace(CharacterRegex, invisibles.space) if invisibles.space
|
||||
leadingHtml = "<span class='#{classes}'>#{match[0]}</span>"
|
||||
|
||||
startIndex = match[0].length
|
||||
|
||||
if hasTrailingWhitespace and match = TrailingWhitespaceRegex.exec(html)
|
||||
classes = 'trailing-whitespace'
|
||||
classes += ' indent-guide' if hasIndentGuide and not hasLeadingWhitespace
|
||||
classes += ' invisible-character' if invisibles.space
|
||||
|
||||
match[0] = match[0].replace(CharacterRegex, invisibles.space) if invisibles.space
|
||||
trailingHtml = "<span class='#{classes}'>#{match[0]}</span>"
|
||||
|
||||
endIndex = match.index
|
||||
|
||||
html = leadingHtml + @escapeString(html, startIndex, endIndex) + trailingHtml
|
||||
|
||||
html
|
||||
|
||||
escapeString: (str, startIndex, endIndex) ->
|
||||
strLength = str.length
|
||||
|
||||
startIndex ?= 0
|
||||
endIndex ?= strLength
|
||||
|
||||
str = str.slice(startIndex, endIndex) if startIndex > 0 or endIndex < strLength
|
||||
str.replace(EscapeRegex, @escapeStringReplace)
|
||||
|
||||
escapeStringReplace: (match) ->
|
||||
switch match
|
||||
when '&' then '&'
|
||||
when '"' then '"'
|
||||
when "'" then '''
|
||||
when '<' then '<'
|
||||
when '>' then '>'
|
||||
else match
|
||||
|
@ -38,7 +38,7 @@ window.setUpEnvironment = (windowMode) ->
|
||||
window.setUpDefaultEvents = ->
|
||||
windowEventHandler = new WindowEventHandler
|
||||
atom.keymap.loadBundledKeymaps()
|
||||
ipc.sendChannel 'update-application-menu', atom.keymap.keystrokesByCommandForSelector('body')
|
||||
atom.menu.update()
|
||||
|
||||
# This method is only called when opening a real application window
|
||||
window.startEditorWindow = ->
|
||||
@ -56,7 +56,7 @@ window.startEditorWindow = ->
|
||||
atom.packages.activatePackages()
|
||||
atom.keymap.loadUserKeymaps()
|
||||
atom.requireUserInitScript()
|
||||
ipc.sendChannel 'update-application-menu', atom.keymap.keystrokesByCommandForSelector('body')
|
||||
atom.menu.update()
|
||||
$(window).on 'unload', ->
|
||||
$(document.body).hide()
|
||||
unloadEditorWindow()
|
||||
|
@ -8,11 +8,7 @@
|
||||
var currentWindow = require('remote').getCurrentWindow();
|
||||
try {
|
||||
require('coffee-script');
|
||||
require('../src/coffee-cache');
|
||||
Object.defineProperty(require.extensions, '.coffee', {
|
||||
writable: false,
|
||||
value: require.extensions['.coffee']
|
||||
});
|
||||
require('../src/coffee-cache').register();
|
||||
require(currentWindow.loadSettings.bootstrapScript);
|
||||
currentWindow.emit('window:loaded');
|
||||
}
|
||||
|
@ -2,10 +2,67 @@ fs = require 'fs'
|
||||
path = require 'path'
|
||||
|
||||
request = require 'request'
|
||||
formidable = require 'formidable'
|
||||
|
||||
module.exports = (grunt) ->
|
||||
{spawn, mkdir, rm, cp} = require('./task-helpers')(grunt)
|
||||
|
||||
accessToken = null
|
||||
getTokenFromKeychain = (callback) ->
|
||||
accessToken ?= process.env['ATOM_ACCESS_TOKEN']
|
||||
if accessToken
|
||||
callback(null, accessToken)
|
||||
return
|
||||
|
||||
spawn {cmd: 'security', args: ['-q', 'find-generic-password', '-ws', 'GitHub API Token']}, (error, result, code) ->
|
||||
accessToken = result unless error?
|
||||
callback(error, accessToken)
|
||||
|
||||
callAtomShellReposApi = (path, callback) ->
|
||||
getTokenFromKeychain (error, accessToken) ->
|
||||
if error
|
||||
callback(error)
|
||||
return
|
||||
|
||||
options =
|
||||
url: "https://api.github.com/repos/atom/atom-shell#{path}"
|
||||
proxy: process.env.http_proxy || process.env.https_proxy
|
||||
headers:
|
||||
authorization: "token #{accessToken}"
|
||||
accept: 'application/vnd.github.manifold-preview'
|
||||
'user-agent': 'Atom'
|
||||
|
||||
request options, (error, response, body) ->
|
||||
if not error?
|
||||
body = JSON.parse(body)
|
||||
error = new Error(body.message) if response.statusCode != 200
|
||||
callback(error, response, body)
|
||||
|
||||
findReleaseIdFromAtomShellVersion = (version, callback) ->
|
||||
callAtomShellReposApi '/releases', (error, response, data) ->
|
||||
if error?
|
||||
grunt.log.error('GitHub API failed to access atom-shell releases')
|
||||
callback(error)
|
||||
else
|
||||
for release in data when release.tag_name is version
|
||||
callback(null, release.id)
|
||||
return
|
||||
grunt.log.error("There is no #{version} release of atom-shell")
|
||||
callback(false)
|
||||
|
||||
getAtomShellDownloadUrl = (version, releaseId, callback) ->
|
||||
callAtomShellReposApi "/releases/#{releaseId}/assets", (error, response, data) ->
|
||||
if error?
|
||||
grunt.log.error("Cannot get assets of atom-shell's #{version} release")
|
||||
callback(error)
|
||||
else
|
||||
filename = "atom-shell-#{version}-#{process.platform}.zip"
|
||||
for asset in data when asset.name is filename and asset.state is 'uploaded'
|
||||
callback(null, asset.url)
|
||||
return
|
||||
grunt.log.error("Cannot get url of atom-shell's release asset")
|
||||
callback(false)
|
||||
|
||||
getAtomShellVersion = ->
|
||||
versionPath = path.join('atom-shell', 'version')
|
||||
if grunt.file.isFile(versionPath)
|
||||
@ -18,28 +75,69 @@ module.exports = (grunt) ->
|
||||
isAtomShellVersionCached = (version) ->
|
||||
grunt.file.isFile(getCachePath(version), 'version')
|
||||
|
||||
downloadAtomShell = (version, callback) ->
|
||||
getDownloadOptions = (version, url, callback) ->
|
||||
options =
|
||||
url: "https://gh-contractor-zcbenz.s3.amazonaws.com/atom-shell/#{version}/atom-shell-#{version}-darwin.zip"
|
||||
url: url
|
||||
followRedirect: false
|
||||
proxy: process.env.http_proxy || process.env.https_proxy
|
||||
|
||||
inputStream = request(options)
|
||||
inputStream.on 'response', (response) ->
|
||||
if response.statusCode is 200
|
||||
grunt.log.writeln("Downloading atom-shell version #{version.cyan}")
|
||||
cacheDirectory = getCachePath(version)
|
||||
rm(cacheDirectory)
|
||||
mkdir(cacheDirectory)
|
||||
cacheFile = path.join(cacheDirectory, 'atom-shell.zip')
|
||||
outputStream = fs.createWriteStream(cacheFile)
|
||||
outputStream.on 'close', -> callback(null, cacheFile)
|
||||
inputStream.pipe(outputStream)
|
||||
else
|
||||
if response.statusCode is 404
|
||||
grunt.log.error("atom-shell #{version.cyan} not found")
|
||||
# Only set headers for GitHub host, the url could also be a S3 link and
|
||||
# setting headers for it would make the request fail.
|
||||
if require('url').parse(url).hostname is 'api.github.com'
|
||||
getTokenFromKeychain (error, accessToken) ->
|
||||
options.headers =
|
||||
authorization: "token #{accessToken}"
|
||||
accept: 'application/octet-stream'
|
||||
'user-agent': 'Atom'
|
||||
|
||||
callback(error, options)
|
||||
else
|
||||
callback(null, options)
|
||||
|
||||
downloadAtomShell = (version, url, callback) ->
|
||||
getDownloadOptions version, url, (error, options) ->
|
||||
if error
|
||||
callback(error)
|
||||
return
|
||||
|
||||
inputStream = request(options)
|
||||
inputStream.on 'response', (response) ->
|
||||
if response.statusCode is 302
|
||||
# Manually handle redirection so headers would not be sent for S3.
|
||||
downloadAtomShell(version, response.headers.location, callback)
|
||||
else if response.statusCode is 200
|
||||
grunt.log.writeln("Downloading atom-shell version #{version.cyan}")
|
||||
cacheDirectory = getCachePath(version)
|
||||
rm(cacheDirectory)
|
||||
mkdir(cacheDirectory)
|
||||
|
||||
form = new formidable.IncomingForm()
|
||||
form.uploadDir = cacheDirectory
|
||||
form.maxFieldsSize = 100 * 1024 * 1024
|
||||
form.on 'file', (name, file) ->
|
||||
cacheFile = path.join(cacheDirectory, 'atom-shell.zip')
|
||||
fs.renameSync(file.path, cacheFile)
|
||||
callback(null, cacheFile)
|
||||
form.parse response, (error) ->
|
||||
if error
|
||||
grunt.log.error("atom-shell #{version.cyan} failed to download")
|
||||
else
|
||||
grunt.log.error("atom-shell #{version.cyan} request failed")
|
||||
callback(false)
|
||||
if response.statusCode is 404
|
||||
grunt.log.error("atom-shell #{version.cyan} not found")
|
||||
else
|
||||
grunt.log.error("atom-shell #{version.cyan} request failed")
|
||||
callback(false)
|
||||
|
||||
downloadAtomShellOfVersion = (version, callback) ->
|
||||
findReleaseIdFromAtomShellVersion version, (error, releaseId) ->
|
||||
if error?
|
||||
callback(error)
|
||||
else
|
||||
getAtomShellDownloadUrl version, releaseId, (error, url) ->
|
||||
if error?
|
||||
callback(error)
|
||||
else
|
||||
downloadAtomShell version, url, callback
|
||||
|
||||
unzipAtomShell = (zipPath, callback) ->
|
||||
grunt.log.writeln('Unzipping atom-shell')
|
||||
@ -48,7 +146,7 @@ module.exports = (grunt) ->
|
||||
spawn {cmd: 'unzip', args: [zipPath, '-d', directoryPath]}, (error) ->
|
||||
rm(zipPath)
|
||||
callback(error)
|
||||
|
||||
|
||||
rebuildNativeModules = (previousVersion, callback) ->
|
||||
newVersion = getAtomShellVersion()
|
||||
if newVersion and newVersion isnt previousVersion
|
||||
@ -74,11 +172,13 @@ module.exports = (grunt) ->
|
||||
installAtomShell(atomShellVersion)
|
||||
rebuildNativeModules(currentAtomShellVersion, done)
|
||||
else
|
||||
downloadAtomShell atomShellVersion, (error, zipPath) ->
|
||||
if zipPath?
|
||||
downloadAtomShellOfVersion atomShellVersion, (error, zipPath) ->
|
||||
if error?
|
||||
done(error)
|
||||
else if zipPath?
|
||||
unzipAtomShell zipPath, (error) ->
|
||||
if error?
|
||||
done(false)
|
||||
done(error)
|
||||
else
|
||||
grunt.log.writeln("Installing atom-shell #{atomShellVersion.cyan}")
|
||||
installAtomShell(atomShellVersion)
|
||||
|
2
vendor/apm
vendored
2
vendor/apm
vendored
@ -1 +1 @@
|
||||
Subproject commit 952b3221bb9755d478b10a0ab0384855f80abffe
|
||||
Subproject commit fcb19e296ca8979a28d2d503c2650f4ef381c8be
|
Loading…
Reference in New Issue
Block a user