Merge branch 'master' into wl-build-on-node-7

This commit is contained in:
Wliu 2017-02-24 13:11:05 -05:00 committed by GitHub
commit 69d12d917b
51 changed files with 821 additions and 368 deletions

View File

@ -1 +1 @@
2.7.12
2.7.13

View File

@ -19,7 +19,9 @@ install:
- npm install -g npm
- script/build --create-debian-package --create-rpm-package --compress-artifacts
script: script/test
script:
- script/lint
- script/test
cache:
directories:

View File

@ -27,6 +27,7 @@ build_script:
- script\build.cmd --code-sign --create-windows-installer --compress-artifacts
test_script:
- script\lint.cmd
- script\test.cmd
deploy: off

View File

@ -26,7 +26,7 @@ export default async function ({test}) {
console.log(text.length / 1024)
let t0 = window.performance.now()
const buffer = new TextBuffer(text)
const buffer = new TextBuffer({text})
const editor = new TextEditor({buffer, largeFileMode: true})
atom.workspace.getActivePane().activateItem(editor)
let t1 = window.performance.now()

View File

@ -0,0 +1,97 @@
/** @babel */
import path from 'path'
import fs from 'fs'
import {TextEditor, TextBuffer} from 'atom'
const SIZES_IN_KB = [
512,
1024,
2048
]
const REPEATED_TEXT = fs.readFileSync(path.join(__dirname, '..', 'spec', 'fixtures', 'sample.js'), 'utf8').replace(/\n/g, '')
const TEXT = REPEATED_TEXT.repeat(Math.ceil(SIZES_IN_KB[SIZES_IN_KB.length - 1] * 1024 / REPEATED_TEXT.length))
export default async function ({test}) {
const data = []
const workspaceElement = atom.views.getView(atom.workspace)
document.body.appendChild(workspaceElement)
atom.packages.loadPackages()
await atom.packages.activate()
console.log(atom.getLoadSettings().resourcePath);
for (let pane of atom.workspace.getPanes()) {
pane.destroy()
}
for (const sizeInKB of SIZES_IN_KB) {
const text = TEXT.slice(0, sizeInKB * 1024)
console.log(text.length / 1024)
let t0 = window.performance.now()
const buffer = new TextBuffer({text})
const editor = new TextEditor({buffer, largeFileMode: true})
editor.setGrammar(atom.grammars.grammarForScopeName('source.js'))
atom.workspace.getActivePane().activateItem(editor)
let t1 = window.performance.now()
data.push({
name: 'Opening a large single-line file',
x: sizeInKB,
duration: t1 - t0
})
const tickDurations = []
for (let i = 0; i < 20; i++) {
await timeout(50)
t0 = window.performance.now()
await timeout(0)
t1 = window.performance.now()
tickDurations[i] = t1 - t0
}
data.push({
name: 'Max time event loop was blocked after opening a large single-line file',
x: sizeInKB,
duration: Math.max(...tickDurations)
})
t0 = window.performance.now()
editor.setCursorScreenPosition(editor.element.screenPositionForPixelPosition({
top: 100,
left: 30
}))
t1 = window.performance.now()
data.push({
name: 'Clicking the editor after opening a large single-line file',
x: sizeInKB,
duration: t1 - t0
})
t0 = window.performance.now()
editor.element.setScrollTop(editor.element.getScrollTop() + 100)
t1 = window.performance.now()
data.push({
name: 'Scrolling down after opening a large single-line file',
x: sizeInKB,
duration: t1 - t0
})
editor.destroy()
buffer.destroy()
await timeout(10000)
}
workspaceElement.remove()
return data
}
function timeout (duration) {
return new Promise((resolve) => setTimeout(resolve, duration))
}

View File

@ -7,7 +7,7 @@ Ubuntu LTS 12.04 64-bit is the recommended platform.
* OS with 64-bit or 32-bit architecture
* C++11 toolchain
* Git
* Node.js 4.4.x or later (we recommend installing it via [nvm](https://github.com/creationix/nvm))
* Node.js 6.x (we recommend installing it via [nvm](https://github.com/creationix/nvm))
* npm 3.10.x or later (run `npm install -g npm`)
* Ensure node-gyp uses python2 (run `npm config set python /usr/bin/python2 -g`, use `sudo` if you didn't install node via nvm)
* Development headers for [GNOME Keyring](https://wiki.gnome.org/Projects/GnomeKeyring).

View File

@ -3,7 +3,7 @@
## Requirements
* macOS 10.8 or later
* Node.js 4.4.x or later (we recommend installing it via [nvm](https://github.com/creationix/nvm))
* Node.js 6.x (we recommend installing it via [nvm](https://github.com/creationix/nvm))
* npm 3.10.x or later (run `npm install -g npm`)
* Command Line Tools for [Xcode](https://developer.apple.com/xcode/downloads/) (run `xcode-select --install` to install)

View File

@ -2,7 +2,7 @@
## Requirements
* Node.js 4.4.x or later (the architecture of node available to the build system will determine whether you build 32-bit or 64-bit Atom)
* Node.js 6.x (the architecture of node available to the build system will determine whether you build 32-bit or 64-bit Atom)
* Python v2.7.x
* The python.exe must be available at `%SystemDrive%\Python27\python.exe`. If it is installed elsewhere create a symbolic link to the directory containing the python.exe using: `mklink /d %SystemDrive%\Python27 D:\elsewhere\Python27`
* 7zip (7z.exe available from the command line) - for creating distribution zip files

View File

@ -1,7 +1,7 @@
{
"name": "atom",
"productName": "Atom",
"version": "1.15.0-dev",
"version": "1.16.0-dev",
"description": "A hackable text editor for the 21st Century.",
"main": "./src/main-process/main.js",
"repository": {
@ -15,10 +15,21 @@
"electronVersion": "1.3.13",
"dependencies": {
"async": "0.2.6",
"atom-keymap": "7.1.19",
"atom-select-list": "0.0.6",
"atom-keymap": "7.1.20",
"atom-select-list": "0.0.12",
"atom-ui": "0.4.1",
"babel-core": "5.8.38",
"babel-core": "6.22.1",
"babel-plugin-add-module-exports": "0.2.1",
"babel-plugin-transform-async-to-generator": "6.22.0",
"babel-plugin-transform-class-properties": "6.23.0",
"babel-plugin-transform-decorators-legacy": "1.3.4",
"babel-plugin-transform-do-expressions": "6.22.0",
"babel-plugin-transform-es2015-modules-commonjs": "6.23.0",
"babel-plugin-transform-export-extensions": "6.22.0",
"babel-plugin-transform-flow-strip-types": "6.22.0",
"babel-plugin-transform-function-bind": "6.22.0",
"babel-plugin-transform-object-rest-spread": "6.23.0",
"babel-plugin-transform-react-jsx": "6.23.0",
"cached-run-in-this-context": "0.4.1",
"chai": "3.5.0",
"chart.js": "^2.3.0",
@ -65,7 +76,7 @@
"sinon": "1.17.4",
"source-map-support": "^0.3.2",
"temp": "0.8.1",
"text-buffer": "10.2.5",
"text-buffer": "10.3.12",
"typescript-simple": "1.0.0",
"underscore-plus": "^1.6.6",
"winreg": "^1.2.1",
@ -82,85 +93,86 @@
"one-light-ui": "1.9.1",
"one-dark-syntax": "1.7.1",
"one-light-syntax": "1.7.1",
"solarized-dark-syntax": "1.1.1",
"solarized-light-syntax": "1.1.1",
"solarized-dark-syntax": "1.1.2",
"solarized-light-syntax": "1.1.2",
"about": "1.7.2",
"archive-view": "0.62.2",
"autocomplete-atom-api": "0.10.0",
"autocomplete-css": "0.14.2",
"autocomplete-css": "0.15.0",
"autocomplete-html": "0.7.2",
"autocomplete-plus": "2.34.2",
"autocomplete-snippets": "1.11.0",
"autoflow": "0.29.0",
"autosave": "0.24.0",
"background-tips": "0.26.1",
"bookmarks": "0.44.0",
"bracket-matcher": "0.85.2",
"command-palette": "0.40.0",
"deprecation-cop": "0.56.1",
"bookmarks": "0.44.1",
"bracket-matcher": "0.85.3",
"command-palette": "0.40.2",
"dalek": "0.2.0",
"deprecation-cop": "0.56.2",
"dev-live-reload": "0.47.0",
"encoding-selector": "0.23.0",
"exception-reporting": "0.40.2",
"find-and-replace": "0.206.0",
"encoding-selector": "0.23.1",
"exception-reporting": "0.41.1",
"find-and-replace": "0.206.3",
"fuzzy-finder": "1.4.1",
"git-diff": "1.3.0",
"git-diff": "1.3.1",
"go-to-line": "0.32.0",
"grammar-selector": "0.49.0",
"image-view": "0.60.0",
"incompatible-packages": "0.26.1",
"keybinding-resolver": "0.36.0",
"line-ending-selector": "0.6.0",
"grammar-selector": "0.49.2",
"image-view": "0.61.0",
"incompatible-packages": "0.27.0",
"keybinding-resolver": "0.36.1",
"line-ending-selector": "0.6.1",
"link": "0.31.2",
"markdown-preview": "0.159.3",
"metrics": "1.1.3",
"markdown-preview": "0.159.7",
"metrics": "1.2.1",
"notifications": "0.66.2",
"open-on-github": "1.2.1",
"package-generator": "1.1.0",
"settings-view": "0.246.0",
"settings-view": "0.247.0",
"snippets": "1.0.5",
"spell-check": "0.70.2",
"spell-check": "0.71.0",
"status-bar": "1.8.1",
"styleguide": "0.49.0",
"styleguide": "0.49.2",
"symbols-view": "0.114.0",
"tabs": "0.104.1",
"timecop": "0.34.0",
"tree-view": "0.213.2",
"timecop": "0.36.0",
"tree-view": "0.214.1",
"update-package-dependencies": "0.10.0",
"welcome": "0.36.0",
"whitespace": "0.36.1",
"wrap-guide": "0.39.0",
"language-c": "0.54.1",
"language-clojure": "0.22.1",
"language-coffee-script": "0.48.2",
"language-csharp": "0.14.1",
"welcome": "0.36.1",
"whitespace": "0.36.2",
"wrap-guide": "0.39.1",
"language-c": "0.56.0",
"language-clojure": "0.22.2",
"language-coffee-script": "0.48.4",
"language-csharp": "0.14.2",
"language-css": "0.42.0",
"language-gfm": "0.88.0",
"language-git": "0.19.0",
"language-go": "0.43.1",
"language-html": "0.47.1",
"language-html": "0.47.2",
"language-hyperlink": "0.16.1",
"language-java": "0.25.0",
"language-javascript": "0.125.1",
"language-java": "0.26.0",
"language-javascript": "0.126.0",
"language-json": "0.18.3",
"language-less": "0.30.1",
"language-make": "0.22.3",
"language-mustache": "0.13.1",
"language-objective-c": "0.15.1",
"language-perl": "0.37.0",
"language-php": "0.37.3",
"language-php": "0.37.4",
"language-property-list": "0.9.0",
"language-python": "0.45.1",
"language-ruby": "0.70.4",
"language-ruby-on-rails": "0.25.1",
"language-python": "0.45.2",
"language-ruby": "0.70.5",
"language-ruby-on-rails": "0.25.2",
"language-sass": "0.57.1",
"language-shellscript": "0.25.0",
"language-source": "0.9.0",
"language-sql": "0.25.2",
"language-sql": "0.25.3",
"language-text": "0.7.1",
"language-todo": "0.29.1",
"language-toml": "0.18.1",
"language-xml": "0.34.15",
"language-yaml": "0.27.2"
"language-xml": "0.34.16",
"language-yaml": "0.28.0"
},
"private": true,
"scripts": {

View File

@ -1,3 +1,3 @@
#!/bin/sh
"$(dirname "$0")/../app/apm/apm.sh" "$@"
"$(dirname "$0")/../app/apm/bin/apm" "$@"

View File

@ -5,16 +5,17 @@ const path = require('path')
const spawnSync = require('./spawn-sync')
module.exports = function (packagedAppPath) {
if (!process.env.ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL) {
if (!process.env.ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL && !process.env.ATOM_MAC_CODE_SIGNING_CERT_PATH) {
console.log('Skipping code signing because the ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL environment variable is not defined'.gray)
return
}
// This declaration is outside of the try block because it is also used for the finally block
const certPath = path.join(os.tmpdir(), 'mac.p12')
try {
let certPath = process.env.ATOM_MAC_CODE_SIGNING_CERT_PATH;
if (!certPath) {
certPath = path.join(os.tmpdir(), 'mac.p12')
downloadFileFromGithub(process.env.ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL, certPath)
}
try {
console.log(`Unlocking keychain ${process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN}`)
const unlockArgs = ['unlock-keychain']
// For signing on local workstations, password could be entered interactively
@ -32,6 +33,18 @@ module.exports = function (packagedAppPath) {
'-T', '/usr/bin/codesign'
])
console.log('Running incantation to suppress dialog when signing on macOS Sierra')
try {
spawnSync('security', [
'set-key-partition-list', '-S', 'apple-tool:,apple:', '-s',
'-k', process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN_PASSWORD,
process.env.ATOM_MAC_CODE_SIGNING_KEYCHAIN
])
} catch (e) {
console.log('Incantation failed... maybe this isn\'t Sierra?');
}
console.log(`Code-signing application at ${packagedAppPath}`)
spawnSync('codesign', [
'--deep', '--force', '--verbose',
@ -39,7 +52,9 @@ module.exports = function (packagedAppPath) {
'--sign', 'Developer ID Application: GitHub', packagedAppPath
], {stdio: 'inherit'})
} finally {
console.log(`Deleting certificate at ${certPath}`)
fs.removeSync(certPath)
if (!process.env.ATOM_MAC_CODE_SIGNING_CERT_PATH) {
console.log(`Deleting certificate at ${certPath}`)
fs.removeSync(certPath)
}
}
}

View File

@ -18,15 +18,19 @@ module.exports = function (packagedAppPath, codeSign) {
iconUrl: `https://raw.githubusercontent.com/atom/atom/master/resources/app-icons/${CONFIG.channel}/atom.ico`,
loadingGif: path.join(CONFIG.repositoryRootPath, 'resources', 'win', 'loading.gif'),
outputDirectory: CONFIG.buildOutputPath,
remoteReleases: `https://atom.io/api/updates${archSuffix}`,
remoteReleases: `https://atom.io/api/updates${archSuffix}?version=${CONFIG.appMetadata.version}`,
setupIcon: path.join(CONFIG.repositoryRootPath, 'resources', 'app-icons', CONFIG.channel, 'atom.ico')
}
const certPath = path.join(os.tmpdir(), 'win.p12')
const signing = codeSign && process.env.ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL
const signing = codeSign && (process.env.ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL || process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH)
let certPath = process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH;
if (signing) {
downloadFileFromGithub(process.env.ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL, certPath)
if (!certPath) {
certPath = path.join(os.tmpdir(), 'win.p12')
downloadFileFromGithub(process.env.ATOM_WIN_CODE_SIGNING_CERT_DOWNLOAD_URL, certPath)
}
var signParams = []
signParams.push(`/f ${certPath}`) // Signing cert file
signParams.push(`/p ${process.env.ATOM_WIN_CODE_SIGNING_CERT_PASSWORD}`) // Signing cert password
@ -39,7 +43,7 @@ module.exports = function (packagedAppPath, codeSign) {
}
const cleanUp = function () {
if (fs.existsSync(certPath)) {
if (fs.existsSync(certPath) && !process.env.ATOM_WIN_CODE_SIGNING_CERT_PATH) {
console.log(`Deleting certificate at ${certPath}`)
fs.removeSync(certPath)
}

View File

@ -3,7 +3,6 @@
"description": "Atom build scripts",
"dependencies": {
"async": "2.0.1",
"babel-core": "5.8.38",
"coffeelint": "1.15.7",
"colors": "1.1.2",
"csslint": "1.0.2",

View File

@ -142,6 +142,11 @@ describe "AtomEnvironment", ->
atom.assert(false, "a == b", (e) -> error = e)
expect(error).toBe errors[0]
describe "if passed metadata", ->
it "assigns the metadata on the assertion failure's error object", ->
atom.assert(false, "a == b", {foo: 'bar'})
expect(errors[0].metadata).toEqual {foo: 'bar'}
describe "if the condition is true", ->
it "does nothing", ->
result = atom.assert(true, "a == b")

View File

@ -1,4 +1,5 @@
path = require 'path'
process = require 'process'
_ = require 'underscore-plus'
grim = require 'grim'
marked = require 'marked'
@ -20,12 +21,15 @@ formatStackTrace = (spec, message='', stackTrace) ->
lines.shift() if message.trim() is errorMatch?[1]?.trim()
for line, index in lines
# Remove prefix of lines matching: at [object Object].<anonymous> (path:1:2)
prefixMatch = line.match(/at \[object Object\]\.<anonymous> \(([^)]+)\)/)
# Remove prefix of lines matching: at .<anonymous> (path:1:2)
prefixMatch = line.match(/at \.<anonymous> \(([^)]+)\)/)
line = "at #{prefixMatch[1]}" if prefixMatch
# Relativize locations to spec directory
lines[index] = line.replace("at #{spec.specDirectory}#{path.sep}", 'at ')
if process.platform is 'win32'
line = line.replace('file:///', '').replace(///#{path.posix.sep}///g, path.win32.sep)
line = line.replace("at #{spec.specDirectory}#{path.sep}", 'at ')
lines[index] = line.replace("(#{spec.specDirectory}#{path.sep}", '(') # at step (path:1:2)
lines = lines.map (line) -> line.trim()
lines.join('\n').trim()

View File

@ -1,13 +1,14 @@
DecorationManager = require '../src/decoration-manager'
describe "DecorationManager", ->
[decorationManager, buffer, defaultMarkerLayer] = []
[decorationManager, buffer, displayLayer, markerLayer1, markerLayer2] = []
beforeEach ->
buffer = atom.project.bufferForPathSync('sample.js')
displayLayer = buffer.addDisplayLayer()
defaultMarkerLayer = displayLayer.addMarkerLayer()
decorationManager = new DecorationManager(displayLayer, defaultMarkerLayer)
markerLayer1 = displayLayer.addMarkerLayer()
markerLayer2 = displayLayer.addMarkerLayer()
decorationManager = new DecorationManager(displayLayer)
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
@ -17,38 +18,53 @@ describe "DecorationManager", ->
buffer.release()
describe "decorations", ->
[marker, decoration, decorationProperties] = []
[layer1Marker, layer2Marker, layer1MarkerDecoration, layer2MarkerDecoration, decorationProperties] = []
beforeEach ->
marker = defaultMarkerLayer.markBufferRange([[2, 13], [3, 15]])
layer1Marker = markerLayer1.markBufferRange([[2, 13], [3, 15]])
decorationProperties = {type: 'line-number', class: 'one'}
decoration = decorationManager.decorateMarker(marker, decorationProperties)
layer1MarkerDecoration = decorationManager.decorateMarker(layer1Marker, decorationProperties)
layer2Marker = markerLayer2.markBufferRange([[2, 13], [3, 15]])
layer2MarkerDecoration = decorationManager.decorateMarker(layer2Marker, decorationProperties)
it "can add decorations associated with markers and remove them", ->
expect(decoration).toBeDefined()
expect(decoration.getProperties()).toBe decorationProperties
expect(decorationManager.decorationForId(decoration.id)).toBe decoration
expect(decorationManager.decorationsForScreenRowRange(2, 3)[marker.id][0]).toBe decoration
expect(layer1MarkerDecoration).toBeDefined()
expect(layer1MarkerDecoration.getProperties()).toBe decorationProperties
expect(decorationManager.decorationForId(layer1MarkerDecoration.id)).toBe layer1MarkerDecoration
expect(decorationManager.decorationsForScreenRowRange(2, 3)).toEqual {
"#{layer1Marker.id}": [layer1MarkerDecoration],
"#{layer2Marker.id}": [layer2MarkerDecoration]
}
decoration.destroy()
expect(decorationManager.decorationsForScreenRowRange(2, 3)[marker.id]).not.toBeDefined()
expect(decorationManager.decorationForId(decoration.id)).not.toBeDefined()
layer1MarkerDecoration.destroy()
expect(decorationManager.decorationsForScreenRowRange(2, 3)[layer1Marker.id]).not.toBeDefined()
expect(decorationManager.decorationForId(layer1MarkerDecoration.id)).not.toBeDefined()
layer2MarkerDecoration.destroy()
expect(decorationManager.decorationsForScreenRowRange(2, 3)[layer2Marker.id]).not.toBeDefined()
expect(decorationManager.decorationForId(layer2MarkerDecoration.id)).not.toBeDefined()
it "will not fail if the decoration is removed twice", ->
decoration.destroy()
decoration.destroy()
expect(decorationManager.decorationForId(decoration.id)).not.toBeDefined()
layer1MarkerDecoration.destroy()
layer1MarkerDecoration.destroy()
expect(decorationManager.decorationForId(layer1MarkerDecoration.id)).not.toBeDefined()
it "does not allow destroyed markers to be decorated", ->
marker.destroy()
layer1Marker.destroy()
expect(->
decorationManager.decorateMarker(marker, {type: 'overlay', item: document.createElement('div')})
decorationManager.decorateMarker(layer1Marker, {type: 'overlay', item: document.createElement('div')})
).toThrow("Cannot decorate a destroyed marker")
expect(decorationManager.getOverlayDecorations()).toEqual []
it "does not allow destroyed marker layers to be decorated", ->
layer = displayLayer.addMarkerLayer()
layer.destroy()
expect(->
decorationManager.decorateMarkerLayer(layer, {type: 'highlight'})
).toThrow("Cannot decorate a destroyed marker layer")
describe "when a decoration is updated via Decoration::update()", ->
it "emits an 'updated' event containing the new and old params", ->
decoration.onDidChangeProperties updatedSpy = jasmine.createSpy()
decoration.setProperties type: 'line-number', class: 'two'
layer1MarkerDecoration.onDidChangeProperties updatedSpy = jasmine.createSpy()
layer1MarkerDecoration.setProperties type: 'line-number', class: 'two'
{oldProperties, newProperties} = updatedSpy.mostRecentCall.args[0]
expect(oldProperties).toEqual decorationProperties
@ -56,29 +72,29 @@ describe "DecorationManager", ->
describe "::getDecorations(properties)", ->
it "returns decorations matching the given optional properties", ->
expect(decorationManager.getDecorations()).toEqual [decoration]
expect(decorationManager.getDecorations()).toEqual [layer1MarkerDecoration, layer2MarkerDecoration]
expect(decorationManager.getDecorations(class: 'two').length).toEqual 0
expect(decorationManager.getDecorations(class: 'one').length).toEqual 1
expect(decorationManager.getDecorations(class: 'one').length).toEqual 2
describe "::decorateMarker", ->
describe "when decorating gutters", ->
[marker] = []
[layer1Marker] = []
beforeEach ->
marker = defaultMarkerLayer.markBufferRange([[1, 0], [1, 0]])
layer1Marker = markerLayer1.markBufferRange([[1, 0], [1, 0]])
it "creates a decoration that is both of 'line-number' and 'gutter' type when called with the 'line-number' type", ->
decorationProperties = {type: 'line-number', class: 'one'}
decoration = decorationManager.decorateMarker(marker, decorationProperties)
expect(decoration.isType('line-number')).toBe true
expect(decoration.isType('gutter')).toBe true
expect(decoration.getProperties().gutterName).toBe 'line-number'
expect(decoration.getProperties().class).toBe 'one'
layer1MarkerDecoration = decorationManager.decorateMarker(layer1Marker, decorationProperties)
expect(layer1MarkerDecoration.isType('line-number')).toBe true
expect(layer1MarkerDecoration.isType('gutter')).toBe true
expect(layer1MarkerDecoration.getProperties().gutterName).toBe 'line-number'
expect(layer1MarkerDecoration.getProperties().class).toBe 'one'
it "creates a decoration that is only of 'gutter' type if called with the 'gutter' type and a 'gutterName'", ->
decorationProperties = {type: 'gutter', gutterName: 'test-gutter', class: 'one'}
decoration = decorationManager.decorateMarker(marker, decorationProperties)
expect(decoration.isType('gutter')).toBe true
expect(decoration.isType('line-number')).toBe false
expect(decoration.getProperties().gutterName).toBe 'test-gutter'
expect(decoration.getProperties().class).toBe 'one'
layer1MarkerDecoration = decorationManager.decorateMarker(layer1Marker, decorationProperties)
expect(layer1MarkerDecoration.isType('gutter')).toBe true
expect(layer1MarkerDecoration.isType('line-number')).toBe false
expect(layer1MarkerDecoration.getProperties().gutterName).toBe 'test-gutter'
expect(layer1MarkerDecoration.getProperties().class).toBe 'one'

View File

@ -28,6 +28,15 @@ describe "DefaultDirectoryProvider", ->
directory = provider.directoryForURISync(nonNormalizedPath)
expect(directory.getPath()).toEqual tmp
it "normalizes disk drive letter in path on #win32", ->
provider = new DefaultDirectoryProvider()
nonNormalizedPath = tmp[0].toLowerCase()+tmp.slice(1)
expect(tmp).not.toMatch /^[a-z]:/
expect(nonNormalizedPath).toMatch /^[a-z]:/
directory = provider.directoryForURISync(nonNormalizedPath)
expect(directory.getPath()).toEqual tmp
it "creates a Directory for its parent dir when passed a file", ->
provider = new DefaultDirectoryProvider()
file = path.join(tmp, "example.txt")

View File

@ -1,60 +0,0 @@
DOMElementPool = require '../src/dom-element-pool'
{contains} = require 'underscore-plus'
describe "DOMElementPool", ->
domElementPool = null
beforeEach ->
domElementPool = new DOMElementPool
it "builds DOM nodes, recycling them when they are freed", ->
[div, span1, span2, span3, span4, span5, textNode] = elements = [
domElementPool.buildElement("div")
domElementPool.buildElement("span")
domElementPool.buildElement("span")
domElementPool.buildElement("span")
domElementPool.buildElement("span")
domElementPool.buildElement("span")
domElementPool.buildText("Hello world!")
]
div.appendChild(span1)
span1.appendChild(span2)
div.appendChild(span3)
span3.appendChild(span4)
span4.appendChild(textNode)
domElementPool.freeElementAndDescendants(div)
domElementPool.freeElementAndDescendants(span5)
expect(contains(elements, domElementPool.buildElement("div"))).toBe(true)
expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
expect(contains(elements, domElementPool.buildText("another text"))).toBe(true)
expect(contains(elements, domElementPool.buildElement("div"))).toBe(false)
expect(contains(elements, domElementPool.buildElement("span"))).toBe(false)
expect(contains(elements, domElementPool.buildText("unexisting"))).toBe(false)
it "forgets free nodes after being cleared", ->
span = domElementPool.buildElement("span")
div = domElementPool.buildElement("div")
domElementPool.freeElementAndDescendants(span)
domElementPool.freeElementAndDescendants(div)
domElementPool.clear()
expect(domElementPool.buildElement("span")).not.toBe(span)
expect(domElementPool.buildElement("div")).not.toBe(div)
it "throws an error when trying to free the same node twice", ->
div = domElementPool.buildElement("div")
domElementPool.freeElementAndDescendants(div)
expect(-> domElementPool.freeElementAndDescendants(div)).toThrow()
it "throws an error when trying to free an invalid element", ->
expect(-> domElementPool.freeElementAndDescendants(null)).toThrow()
expect(-> domElementPool.freeElementAndDescendants(undefined)).toThrow()

View File

@ -0,0 +1,112 @@
const DOMElementPool = require ('../src/dom-element-pool')
describe('DOMElementPool', function () {
let domElementPool
beforeEach(() => { domElementPool = new DOMElementPool() })
it('builds DOM nodes, recycling them when they are freed', function () {
let elements
const [div, span1, span2, span3, span4, span5, textNode] = Array.from(elements = [
domElementPool.buildElement('div', 'foo'),
domElementPool.buildElement('span'),
domElementPool.buildElement('span'),
domElementPool.buildElement('span'),
domElementPool.buildElement('span'),
domElementPool.buildElement('span'),
domElementPool.buildText('Hello world!')
])
expect(div.className).toBe('foo')
div.textContent = 'testing'
div.style.backgroundColor = 'red'
div.dataset.foo = 'bar'
expect(textNode.textContent).toBe('Hello world!')
div.appendChild(span1)
span1.appendChild(span2)
div.appendChild(span3)
span3.appendChild(span4)
span4.appendChild(textNode)
domElementPool.freeElementAndDescendants(div)
domElementPool.freeElementAndDescendants(span5)
expect(elements.includes(domElementPool.buildElement('div'))).toBe(true)
expect(elements.includes(domElementPool.buildElement('span'))).toBe(true)
expect(elements.includes(domElementPool.buildElement('span'))).toBe(true)
expect(elements.includes(domElementPool.buildElement('span'))).toBe(true)
expect(elements.includes(domElementPool.buildElement('span'))).toBe(true)
expect(elements.includes(domElementPool.buildElement('span'))).toBe(true)
expect(elements.includes(domElementPool.buildText('another text'))).toBe(true)
expect(elements.includes(domElementPool.buildElement('div'))).toBe(false)
expect(elements.includes(domElementPool.buildElement('span'))).toBe(false)
expect(elements.includes(domElementPool.buildText('unexisting'))).toBe(false)
expect(div.className).toBe('')
expect(div.textContent).toBe('')
expect(div.style.backgroundColor).toBe('')
expect(div.dataset.foo).toBeUndefined()
expect(textNode.textContent).toBe('another text')
})
it('forgets free nodes after being cleared', function () {
const span = domElementPool.buildElement('span')
const div = domElementPool.buildElement('div')
domElementPool.freeElementAndDescendants(span)
domElementPool.freeElementAndDescendants(div)
domElementPool.clear()
expect(domElementPool.buildElement('span')).not.toBe(span)
expect(domElementPool.buildElement('div')).not.toBe(div)
})
it('does not attempt to free nodes that were not created by the pool', () => {
let assertionFailure
atom.onDidFailAssertion((error) => assertionFailure = error)
const foreignDiv = document.createElement('div')
const div = domElementPool.buildElement('div')
div.appendChild(foreignDiv)
domElementPool.freeElementAndDescendants(div)
const span = domElementPool.buildElement('span')
span.appendChild(foreignDiv)
domElementPool.freeElementAndDescendants(span)
expect(assertionFailure).toBeUndefined()
})
it('fails an assertion when freeing the same element twice', function () {
let assertionFailure
atom.onDidFailAssertion((error) => assertionFailure = error)
const div = domElementPool.buildElement('div')
div.textContent = 'testing'
domElementPool.freeElementAndDescendants(div)
expect(assertionFailure).toBeUndefined()
domElementPool.freeElementAndDescendants(div)
expect(assertionFailure.message).toBe('Assertion failed: The element has already been freed!')
expect(assertionFailure.metadata.content).toBe('<div>testing</div>')
})
it('fails an assertion when freeing the same text node twice', function () {
let assertionFailure
atom.onDidFailAssertion((error) => assertionFailure = error)
const node = domElementPool.buildText('testing')
domElementPool.freeElementAndDescendants(node)
expect(assertionFailure).toBeUndefined()
domElementPool.freeElementAndDescendants(node)
expect(assertionFailure.message).toBe('Assertion failed: The element has already been freed!')
expect(assertionFailure.metadata.content).toBe('testing')
})
it('throws an error when trying to free an invalid element', function () {
expect(() => domElementPool.freeElementAndDescendants(null)).toThrow()
expect(() => domElementPool.freeElementAndDescendants(undefined)).toThrow()
})
})

View File

@ -196,7 +196,9 @@ describe('AtomApplication', function () {
it('persists window state based on the project directories', async function () {
const tempDirPath = makeTempDir()
const atomApplication = buildAtomApplication()
const window1 = atomApplication.launch(parseCommandLine([path.join(tempDirPath, 'new-file')]))
const nonExistentFilePath = path.join(tempDirPath, 'new-file')
const window1 = atomApplication.launch(parseCommandLine([nonExistentFilePath]))
await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
atom.workspace.observeActivePaneItem(function (textEditor) {
if (textEditor) {
@ -205,17 +207,31 @@ describe('AtomApplication', function () {
}
})
})
await window1.saveState()
window1.close()
await window1.closedPromise
const window2 = atomApplication.launch(parseCommandLine([path.join(tempDirPath)]))
// Restore unsaved state when opening the directory itself
const window2 = atomApplication.launch(parseCommandLine([tempDirPath]))
await window2.loadedPromise
const window2Text = await evalInWebContents(window2.browserWindow.webContents, function (sendBackToMainProcess) {
atom.workspace.observeActivePaneItem(function (textEditor) {
if (textEditor) sendBackToMainProcess(textEditor.getText())
})
const textEditor = atom.workspace.getActiveTextEditor()
textEditor.moveToBottom()
textEditor.insertText(' How are you?')
sendBackToMainProcess(textEditor.getText())
})
assert.equal(window2Text, 'Hello World! How are you?')
await window2.saveState()
window2.close()
await window2.closedPromise
assert.equal(window2Text, 'Hello World!')
// Restore unsaved state when opening a path to a non-existent file in the directory
const window3 = atomApplication.launch(parseCommandLine([path.join(tempDirPath, 'another-non-existent-file')]))
await window3.loadedPromise
const window3Texts = await evalInWebContents(window3.browserWindow.webContents, function (sendBackToMainProcess, nonExistentFilePath) {
sendBackToMainProcess(atom.workspace.getTextEditors().map(editor => editor.getText()))
})
assert.include(window3Texts, 'Hello World! How are you?')
})
it('shows all directories in the tree view when multiple directory paths are passed to Atom', async function () {
@ -260,7 +276,7 @@ describe('AtomApplication', function () {
})
assert.equal(window1EditorTitle, 'untitled')
const window2 = atomApplication.launch(parseCommandLine([]))
const window2 = atomApplication.openWithOptions(parseCommandLine([]))
await focusWindow(window2)
const window2EditorTitle = await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
sendBackToMainProcess(atom.workspace.getActiveTextEditor().getTitle())
@ -472,7 +488,7 @@ describe('AtomApplication', function () {
}
let channelIdCounter = 0
function evalInWebContents (webContents, source) {
function evalInWebContents (webContents, source, ...args) {
const channelId = 'eval-result-' + channelIdCounter++
return new Promise(function (resolve) {
electron.ipcMain.on(channelId, receiveResult)

View File

@ -112,7 +112,7 @@ describe("FileRecoveryService", () => {
const mockWindow = {}
const filePath = temp.path()
fs.writeFileSync(filePath, "content")
fs.chmodSync(filePath, 0444)
fs.chmodSync(filePath, 0o444)
let logs = []
this.stub(console, 'log', (message) => logs.push(message))

View File

@ -614,3 +614,7 @@ describe "Project", ->
randomPath = path.join("some", "random", "path")
expect(atom.project.contains(randomPath)).toBe false
describe ".resolvePath(uri)", ->
it "normalizes disk drive letter in passed path on #win32", ->
expect(atom.project.resolvePath("d:\\file.txt")).toEqual "D:\\file.txt"

View File

@ -1747,11 +1747,13 @@ describe('TextEditorComponent', function () {
})
describe('block decorations rendering', function () {
let markerLayer
function createBlockDecorationBeforeScreenRow(screenRow, {className}) {
let item = document.createElement("div")
item.className = className || ""
let blockDecoration = editor.decorateMarker(
editor.markScreenPosition([screenRow, 0], {invalidate: "never"}),
markerLayer.markScreenPosition([screenRow, 0], {invalidate: "never"}),
{type: "block", item: item, position: "before"}
)
return [item, blockDecoration]
@ -1761,13 +1763,14 @@ describe('TextEditorComponent', function () {
let item = document.createElement("div")
item.className = className || ""
let blockDecoration = editor.decorateMarker(
editor.markScreenPosition([screenRow, 0], {invalidate: "never"}),
markerLayer.markScreenPosition([screenRow, 0], {invalidate: "never"}),
{type: "block", item: item, position: "after"}
)
return [item, blockDecoration]
}
beforeEach(function () {
markerLayer = editor.addMarkerLayer()
wrapperNode.style.height = 5 * lineHeightInPixels + 'px'
editor.update({autoHeight: false})
component.measureDimensions()

View File

@ -886,8 +886,12 @@ describe "Workspace", ->
describe "document.title", ->
describe "when there is no item open", ->
it "sets the title to 'untitled'", ->
expect(document.title).toMatch ///^untitled///
it "sets the title to the project path", ->
expect(document.title).toMatch escapeStringRegex(fs.tildify(atom.project.getPaths()[0]))
it "sets the title to 'untitled' if there is no project path", ->
atom.project.setPaths([])
expect(document.title).toMatch /^untitled/
describe "when the active pane item's path is not inside a project path", ->
beforeEach ->
@ -948,10 +952,10 @@ describe "Workspace", ->
expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{pathEscaped}///
describe "when the last pane item is removed", ->
it "updates the title to be untitled", ->
it "updates the title to the project's first path", ->
atom.workspace.getActivePane().destroy()
expect(atom.workspace.getActivePaneItem()).toBeUndefined()
expect(document.title).toMatch ///^untitled///
expect(document.title).toMatch escapeStringRegex(fs.tildify(atom.project.getPaths()[0]))
describe "when an inactive pane's item changes", ->
it "does not update the title", ->

View File

@ -2,10 +2,12 @@ _ = require 'underscore-plus'
{screen, ipcRenderer, remote, shell, webFrame} = require 'electron'
ipcHelpers = require './ipc-helpers'
{Disposable} = require 'event-kit'
{getWindowLoadSettings, setWindowLoadSettings} = require './window-load-settings-helpers'
getWindowLoadSettings = require './get-window-load-settings'
module.exports =
class ApplicationDelegate
getWindowLoadSettings: -> getWindowLoadSettings()
open: (params) ->
ipcRenderer.send('open', params)
@ -109,10 +111,7 @@ class ApplicationDelegate
ipcRenderer.send("add-recent-document", filename)
setRepresentedDirectoryPaths: (paths) ->
loadSettings = getWindowLoadSettings()
loadSettings['initialPaths'] = paths
setWindowLoadSettings(loadSettings)
ipcRenderer.send("did-change-paths")
ipcHelpers.call('window-method', 'setRepresentedDirectoryPaths', paths)
setAutoHideWindowMenuBar: (autoHide) ->
ipcHelpers.call('window-method', 'setAutoHideMenuBar', autoHide)
@ -149,13 +148,9 @@ class ApplicationDelegate
showMessageDialog: (params) ->
showSaveDialog: (params) ->
if _.isString(params)
params = defaultPath: params
else
params = _.clone(params)
params.title ?= 'Save File'
params.defaultPath ?= getWindowLoadSettings().initialPaths[0]
remote.dialog.showSaveDialog remote.getCurrentWindow(), params
if typeof params is 'string'
params = {defaultPath: params}
@getCurrentWindow().showSaveDialog(params)
playBeepSound: ->
shell.beep()

View File

@ -11,7 +11,6 @@ Model = require './model'
WindowEventHandler = require './window-event-handler'
StateStore = require './state-store'
StorageFolder = require './storage-folder'
{getWindowLoadSettings} = require './window-load-settings-helpers'
registerDefaultCommands = require './register-default-commands'
{updateProcessEnv} = require './update-process-env'
@ -458,7 +457,7 @@ class AtomEnvironment extends Model
#
# Returns an {Object} containing all the load setting key/value pairs.
getLoadSettings: ->
getWindowLoadSettings()
@applicationDelegate.getWindowLoadSettings()
###
Section: Managing The Atom Window
@ -695,8 +694,14 @@ class AtomEnvironment extends Model
@deserialize(state) if state?
@deserializeTimings.atom = Date.now() - startTime
if process.platform is 'darwin' and @config.get('core.useCustomTitleBar')
if process.platform is 'darwin' and @config.get('core.titleBar') is 'custom'
@workspace.addHeaderPanel({item: new TitleBar({@workspace, @themes, @applicationDelegate})})
@document.body.classList.add('custom-title-bar')
if process.platform is 'darwin' and @config.get('core.titleBar') is 'custom-inset'
@workspace.addHeaderPanel({item: new TitleBar({@workspace, @themes, @applicationDelegate})})
@document.body.classList.add('custom-inset-title-bar')
if process.platform is 'darwin' and @config.get('core.titleBar') is 'hidden'
@document.body.classList.add('hidden-title-bar')
@document.body.appendChild(@views.getView(@workspace))
@backgroundStylesheet?.remove()
@ -822,12 +827,17 @@ class AtomEnvironment extends Model
Section: Private
###
assert: (condition, message, callback) ->
assert: (condition, message, callbackOrMetadata) ->
return true if condition
error = new Error("Assertion failed: #{message}")
Error.captureStackTrace(error, @assert)
callback?(error)
if callbackOrMetadata?
if typeof callbackOrMetadata is 'function'
callbackOrMetadata?(error)
else
error.metadata = callbackOrMetadata
@emitter.emit 'did-fail-assertion', error

View File

@ -6,6 +6,7 @@ var defaultOptions = require('../static/babelrc.json')
var babel = null
var babelVersionDirectory = null
var options = null
var PREFIXES = [
'/** @babel */',
@ -47,16 +48,27 @@ exports.compile = function (sourceCode, filePath) {
var noop = function () {}
Logger.prototype.debug = noop
Logger.prototype.verbose = noop
options = {ast: false, babelrc: false}
for (var key in defaultOptions) {
if (key === 'plugins') {
const plugins = []
for (const [pluginName, pluginOptions] of defaultOptions[key]) {
plugins.push([require.resolve(`babel-plugin-${pluginName}`), pluginOptions])
}
options[key] = plugins
} else {
options[key] = defaultOptions[key]
}
}
}
if (process.platform === 'win32') {
filePath = 'file:///' + path.resolve(filePath).replace(/\\/g, '/')
}
var options = {filename: filePath}
for (var key in defaultOptions) {
options[key] = defaultOptions[key]
}
options.filename = filePath
return babel.transform(sourceCode, options).code
}

View File

@ -68,6 +68,12 @@ const configSchema = {
default: true,
description: 'Trigger the system\'s beep sound when certain actions cannot be executed or there are no results.'
},
closeDeletedFileTabs: {
type: 'boolean',
default: false,
title: 'Close Deleted File Tabs',
description: 'Close corresponding editors when a file is deleted outside Atom.'
},
destroyEmptyPanes: {
type: 'boolean',
default: true,
@ -492,10 +498,11 @@ if (['win32', 'linux'].includes(process.platform)) {
}
if (process.platform === 'darwin') {
configSchema.core.properties.useCustomTitleBar = {
type: 'boolean',
default: false,
description: 'Use custom, theme-aware title bar.<br>Note: This currently does not include a proxy icon.<br>This setting will require a relaunch of Atom to take effect.'
configSchema.core.properties.titleBar = {
type: 'string',
default: 'native',
enum: ['native', 'custom', 'custom-inset', 'hidden'],
description: 'Experimental: A `custom` title bar adapts to theme colors. Choosing `custom-inset` adds a bit more padding. The title bar can also be completely `hidden`.<br>Note: Switching to a custom or hidden title bar will compromise some functionality.<br>This setting will require a relaunch of Atom to take effect.'
}
}

View File

@ -336,6 +336,31 @@ ScopeDescriptor = require './scope-descriptor'
# order: 2
# ```
#
# ## Manipulating values outside your configuration schema
#
# It is possible to manipulate(`get`, `set`, `observe` etc) values that do not
# appear in your configuration schema. For example, if the config schema of the
# package 'some-package' is
#
# ```coffee
# config:
# someSetting:
# type: 'boolean'
# default: false
# ```
#
# You can still do the following
#
# ```coffee
# let otherSetting = atom.config.get('some-package.otherSetting')
# atom.config.set('some-package.stillAnotherSetting', otherSetting * 5)
# ```
#
# In other words, if a function asks for a `key-path`, that path doesn't have to
# be described in the config schema for the package or any package. However, as
# highlighted in the best practices section, you are advised against doing the
# above.
#
# ## Best practices
#
# * Don't depend on (or write to) configuration keys outside of your keypath.

View File

@ -8,7 +8,7 @@ class DecorationManager extends Model
didUpdateDecorationsEventScheduled: false
updatedSynchronously: false
constructor: (@displayLayer, @defaultMarkerLayer) ->
constructor: (@displayLayer) ->
super
@emitter = new Emitter
@ -71,9 +71,11 @@ class DecorationManager extends Model
decorationsForScreenRowRange: (startScreenRow, endScreenRow) ->
decorationsByMarkerId = {}
for marker in @defaultMarkerLayer.findMarkers(intersectsScreenRowRange: [startScreenRow, endScreenRow])
if decorations = @decorationsByMarkerId[marker.id]
decorationsByMarkerId[marker.id] = decorations
for layerId of @decorationCountsByLayerId
layer = @displayLayer.getMarkerLayer(layerId)
for marker in layer.findMarkers(intersectsScreenRowRange: [startScreenRow, endScreenRow])
if decorations = @decorationsByMarkerId[marker.id]
decorationsByMarkerId[marker.id] = decorations
decorationsByMarkerId
decorationsStateForScreenRowRange: (startScreenRow, endScreenRow) ->
@ -104,7 +106,14 @@ class DecorationManager extends Model
decorationsState
decorateMarker: (marker, decorationParams) ->
throw new Error("Cannot decorate a destroyed marker") if marker.isDestroyed()
if marker.isDestroyed()
error = new Error("Cannot decorate a destroyed marker")
error.metadata = {markerLayerIsDestroyed: marker.layer.isDestroyed()}
if marker.destroyStackTrace?
error.metadata.destroyStackTrace = marker.destroyStackTrace
if marker.bufferMarker?.destroyStackTrace?
error.metadata.destroyStackTrace = marker.bufferMarker?.destroyStackTrace
throw error
marker = @displayLayer.getMarkerLayer(marker.layer.id).getMarker(marker.id)
decoration = new Decoration(marker, this, decorationParams)
@decorationsByMarkerId[marker.id] ?= []
@ -117,6 +126,7 @@ class DecorationManager extends Model
decoration
decorateMarkerLayer: (markerLayer, decorationParams) ->
throw new Error("Cannot decorate a destroyed marker layer") if markerLayer.isDestroyed()
decoration = new LayerDecoration(markerLayer, this, decorationParams)
@layerDecorationsByMarkerLayerId[markerLayer.id] ?= []
@layerDecorationsByMarkerLayerId[markerLayer.id].push(decoration)

View File

@ -15,7 +15,7 @@ class DefaultDirectoryProvider
# * {Directory} if the given URI is compatible with this provider.
# * `null` if the given URI is not compatibile with this provider.
directoryForURISync: (uri) ->
normalizedPath = path.normalize(uri)
normalizedPath = @normalizePath(uri)
{host} = url.parse(uri)
directoryPath = if host
uri
@ -42,3 +42,17 @@ class DefaultDirectoryProvider
# * `null` if the given URI is not compatibile with this provider.
directoryForURI: (uri) ->
Promise.resolve(@directoryForURISync(uri))
# Public: Normalizes path.
#
# * `uri` {String} The path that should be normalized.
#
# Returns a {String} with normalized path.
normalizePath: (uri) ->
# Normalize disk drive letter on Windows to avoid opening two buffers for the same file
pathWithNormalizedDiskDriveLetter =
if process.platform is 'win32' and matchData = uri.match(/^([a-z]):/)
"#{matchData[1].toUpperCase()}#{uri.slice(1)}"
else
uri
path.normalize(pathWithNormalizedDiskDriveLetter)

View File

@ -1,55 +0,0 @@
module.exports =
class DOMElementPool
constructor: ->
@freeElementsByTagName = {}
@freedElements = new Set
clear: ->
@freedElements.clear()
for tagName, freeElements of @freeElementsByTagName
freeElements.length = 0
return
build: (tagName, factory, reset) ->
element = @freeElementsByTagName[tagName]?.pop()
element ?= factory()
reset(element)
@freedElements.delete(element)
element
buildElement: (tagName, className) ->
factory = -> document.createElement(tagName)
reset = (element) ->
delete element.dataset[dataId] for dataId of element.dataset
element.removeAttribute("style")
if className?
element.className = className
else
element.removeAttribute("class")
@build(tagName, factory, reset)
buildText: (textContent) ->
factory = -> document.createTextNode(textContent)
reset = (element) -> element.textContent = textContent
@build("#text", factory, reset)
freeElementAndDescendants: (element) ->
@free(element)
@freeDescendants(element)
freeDescendants: (element) ->
for descendant in element.childNodes by -1
@free(descendant)
@freeDescendants(descendant)
return
free: (element) ->
throw new Error("The element cannot be null or undefined.") unless element?
throw new Error("The element has already been freed!") if @freedElements.has(element)
tagName = element.nodeName.toLowerCase()
@freeElementsByTagName[tagName] ?= []
@freeElementsByTagName[tagName].push(element)
@freedElements.add(element)
element.remove()

89
src/dom-element-pool.js Normal file
View File

@ -0,0 +1,89 @@
module.exports =
class DOMElementPool {
constructor () {
this.managedElements = new Set()
this.freeElementsByTagName = new Map()
this.freedElements = new Set()
}
clear () {
this.managedElements.clear()
this.freedElements.clear()
this.freeElementsByTagName.clear()
}
buildElement (tagName, className) {
const elements = this.freeElementsByTagName.get(tagName)
let element = elements ? elements.pop() : null
if (element) {
for (let dataId in element.dataset) { delete element.dataset[dataId] }
element.removeAttribute('style')
if (className) {
element.className = className
} else {
element.removeAttribute('class')
}
while (element.firstChild) {
element.removeChild(element.firstChild)
}
this.freedElements.delete(element)
} else {
element = document.createElement(tagName)
if (className) {
element.className = className
}
this.managedElements.add(element)
}
return element
}
buildText (textContent) {
const elements = this.freeElementsByTagName.get('#text')
let element = elements ? elements.pop() : null
if (element) {
element.textContent = textContent
this.freedElements.delete(element)
} else {
element = document.createTextNode(textContent)
this.managedElements.add(element)
}
return element
}
freeElementAndDescendants (element) {
this.free(element)
element.remove()
}
freeDescendants (element) {
while (element.firstChild) {
this.free(element.firstChild)
element.removeChild(element.firstChild)
}
}
free (element) {
if (element == null) { throw new Error('The element cannot be null or undefined.') }
if (!this.managedElements.has(element)) return
if (this.freedElements.has(element)) {
atom.assert(false, 'The element has already been freed!', {
content: element instanceof window.Text ? element.textContent : element.outerHTML
})
return
}
const tagName = element.nodeName.toLowerCase()
let elements = this.freeElementsByTagName.get(tagName)
if (!elements) {
elements = []
this.freeElementsByTagName.set(tagName, elements)
}
elements.push(element)
this.freedElements.add(element)
for (let i = element.childNodes.length - 1; i >= 0; i--) {
const descendant = element.childNodes[i]
this.free(descendant)
}
}
}

View File

@ -0,0 +1,10 @@
const {remote} = require('electron')
let windowLoadSettings = null
module.exports = () => {
if (!windowLoadSettings) {
windowLoadSettings = remote.getCurrentWindow().loadSettings
}
return windowLoadSettings
}

View File

@ -3,7 +3,7 @@ module.exports = ({blobStore}) ->
{updateProcessEnv} = require('./update-process-env')
path = require 'path'
require './window'
{getWindowLoadSettings} = require './window-load-settings-helpers'
getWindowLoadSettings = require './get-window-load-settings'
{ipcRenderer} = require 'electron'
{resourcePath, devMode, env} = getWindowLoadSettings()
require './electron-shims'

View File

@ -6,7 +6,7 @@ import ipcHelpers from './ipc-helpers'
import util from 'util'
export default async function () {
const {getWindowLoadSettings} = require('./window-load-settings-helpers')
const getWindowLoadSettings = require('./get-window-load-settings')
const {test, headless, resourcePath, benchmarkPaths} = getWindowLoadSettings()
try {
const Clipboard = require('../src/clipboard')

View File

@ -18,7 +18,7 @@ module.exports = ({blobStore}) ->
try
path = require 'path'
{ipcRenderer} = require 'electron'
{getWindowLoadSettings} = require './window-load-settings-helpers'
getWindowLoadSettings = require './get-window-load-settings'
CompileCache = require './compile-cache'
AtomEnvironment = require '../src/atom-environment'
ApplicationDelegate = require '../src/application-delegate'

View File

@ -84,7 +84,13 @@ class AtomApplication
initialize: (options) ->
global.atomApplication = this
@config.onDidChange 'core.useCustomTitleBar', @promptForRestart.bind(this)
# DEPRECATED: This can be removed at some point (added in 1.13)
# It converts `useCustomTitleBar: true` to `titleBar: "custom"`
if process.platform is 'darwin' and @config.get('core.useCustomTitleBar')
@config.unset('core.useCustomTitleBar')
@config.set('core.titleBar', 'custom')
@config.onDidChange 'core.titleBar', @promptForRestart.bind(this)
@autoUpdateManager = new AutoUpdateManager(
@version, options.test or options.benchmark or options.benchmarkTest, @resourcePath, @config
@ -587,8 +593,7 @@ class AtomApplication
states = []
for window in @windows
unless window.isSpec
if loadSettings = window.getLoadSettings()
states.push(initialPaths: loadSettings.initialPaths)
states.push({initialPaths: window.representedDirectoryPaths})
if states.length > 0 or allowEmpty
@storageFolder.storeSync('application.json', states)

View File

@ -43,12 +43,16 @@ class AtomWindow
if process.platform is 'linux'
options.icon = @constructor.iconPath
if @shouldHideTitleBar()
if @shouldAddCustomTitleBar()
options.titleBarStyle = 'hidden'
@browserWindow = new BrowserWindow options
@atomApplication.addWindow(this)
if @shouldAddCustomInsetTitleBar()
options.titleBarStyle = 'hidden-inset'
if @shouldHideTitleBar()
options.frame = false
@browserWindow = new BrowserWindow(options)
@handleEvents()
loadSettings = Object.assign({}, settings)
@ -60,11 +64,15 @@ class AtomWindow
loadSettings.clearWindowState ?= false
loadSettings.initialPaths ?=
for {pathToOpen} in locationsToOpen when pathToOpen
if fs.statSyncNoException(pathToOpen).isFile?()
path.dirname(pathToOpen)
else
stat = fs.statSyncNoException(pathToOpen) or null
if stat?.isDirectory()
pathToOpen
else
parentDirectory = path.dirname(pathToOpen)
if stat?.isFile() or fs.existsSync(parentDirectory)
parentDirectory
else
pathToOpen
loadSettings.initialPaths.sort()
# Only send to the first non-spec window created
@ -72,33 +80,31 @@ class AtomWindow
@constructor.includeShellLoadTime = false
loadSettings.shellLoadTime ?= Date.now() - global.shellStartTime
@representedDirectoryPaths = loadSettings.initialPaths
@env = loadSettings.env if loadSettings.env?
@browserWindow.loadSettings = loadSettings
@browserWindow.on 'window:loaded', =>
@emit 'window:loaded'
@resolveLoadedPromise()
@setLoadSettings(loadSettings)
@env = loadSettings.env if loadSettings.env?
@browserWindow.loadURL url.format
protocol: 'file'
pathname: "#{@resourcePath}/static/index.html"
slashes: true
@browserWindow.showSaveDialog = @showSaveDialog.bind(this)
@browserWindow.focusOnWebView() if @isSpec
@browserWindow.temporaryState = {windowDimensions} if windowDimensions?
hasPathToOpen = not (locationsToOpen.length is 1 and not locationsToOpen[0].pathToOpen?)
@openLocations(locationsToOpen) if hasPathToOpen and not @isSpecWindow()
setLoadSettings: (loadSettings) ->
@browserWindow.loadURL url.format
protocol: 'file'
pathname: "#{@resourcePath}/static/index.html"
slashes: true
hash: encodeURIComponent(JSON.stringify(loadSettings))
@atomApplication.addWindow(this)
getLoadSettings: ->
if @browserWindow.webContents? and not @browserWindow.webContents.isLoading()
hash = url.parse(@browserWindow.webContents.getURL()).hash.substr(1)
JSON.parse(decodeURIComponent(hash))
hasProjectPath: -> @getLoadSettings().initialPaths?.length > 0
hasProjectPath: -> @representedDirectoryPaths.length > 0
setupContextMenu: ->
ContextMenu = require './context-menu'
@ -112,7 +118,7 @@ class AtomWindow
true
containsPath: (pathToCheck) ->
@getLoadSettings()?.initialPaths?.some (projectPath) ->
@representedDirectoryPaths.some (projectPath) ->
if not projectPath
false
else if not pathToCheck
@ -226,10 +232,20 @@ class AtomWindow
[width, height] = @browserWindow.getSize()
{x, y, width, height}
shouldAddCustomTitleBar: ->
not @isSpec and
process.platform is 'darwin' and
@atomApplication.config.get('core.titleBar') is 'custom'
shouldAddCustomInsetTitleBar: ->
not @isSpec and
process.platform is 'darwin' and
@atomApplication.config.get('core.titleBar') is 'custom-inset'
shouldHideTitleBar: ->
not @isSpec and
process.platform is 'darwin' and
@atomApplication.config.get('core.useCustomTitleBar')
@atomApplication.config.get('core.titleBar') is 'hidden'
close: -> @browserWindow.close()
@ -265,6 +281,13 @@ class AtomWindow
@saveState().then => @browserWindow.reload()
@loadedPromise
showSaveDialog: (params) ->
params = Object.assign({
title: 'Save File',
defaultPath: @representedDirectoryPaths[0]
}, params)
dialog.showSaveDialog(this, params)
toggleDevTools: -> @browserWindow.toggleDevTools()
openDevTools: -> @browserWindow.openDevTools()
@ -275,4 +298,8 @@ class AtomWindow
setRepresentedFilename: (representedFilename) -> @browserWindow.setRepresentedFilename(representedFilename)
setRepresentedDirectoryPaths: (@representedDirectoryPaths) ->
@representedDirectoryPaths.sort()
@atomApplication.saveState()
copy: -> @browserWindow.copy()

View File

@ -22,7 +22,7 @@ class AutoUpdateManager
setupAutoUpdater: ->
if process.platform is 'win32'
archSuffix = if process.arch is 'ia32' then '' else '-' + process.arch
@feedUrl = "https://atom.io/api/updates#{archSuffix}"
@feedUrl = "https://atom.io/api/updates#{archSuffix}?version=#{@version}"
autoUpdater = require './auto-updater-win32'
else
@feedUrl = "https://atom.io/api/updates?version=#{@version}"

View File

@ -106,14 +106,14 @@ module.exports = function parseCommandLine (processArgs) {
if (args['resource-path']) {
devMode = true
resourcePath = args['resource-path']
devResourcePath = args['resource-path']
}
if (test) {
devMode = true
}
if (devMode && !resourcePath) {
if (devMode) {
resourcePath = devResourcePath
}

View File

@ -21,7 +21,6 @@ class Project extends Model
constructor: ({@notificationManager, packageManager, config, @applicationDelegate}) ->
@emitter = new Emitter
@buffers = []
@paths = []
@rootDirectories = []
@repositories = []
@directoryProviders = []
@ -32,7 +31,9 @@ class Project extends Model
destroyed: ->
buffer.destroy() for buffer in @buffers
@setPaths([])
repository?.destroy() for repository in @repositories
@rootDirectories = []
@repositories = []
reset: (packageManager) ->
@emitter.dispose()
@ -62,6 +63,9 @@ class Project extends Model
fs.closeSync(fs.openSync(bufferState.filePath, 'r'))
catch error
return unless error.code is 'ENOENT'
unless bufferState.shouldDestroyOnFileDelete?
bufferState.shouldDestroyOnFileDelete =
-> atom.config.get('core.closeDeletedFileTabs')
TextBuffer.deserialize(bufferState)
@subscribeToBuffer(buffer) for buffer in @buffers
@ -205,7 +209,7 @@ class Project extends Model
removePath: (projectPath) ->
# The projectPath may be a URI, in which case it should not be normalized.
unless projectPath in @getPaths()
projectPath = path.normalize(projectPath)
projectPath = @defaultDirectoryProvider.normalizePath(projectPath)
indexToRemove = null
for directory, i in @rootDirectories
@ -233,11 +237,10 @@ class Project extends Model
uri
else
if fs.isAbsolute(uri)
path.normalize(fs.resolveHome(uri))
@defaultDirectoryProvider.normalizePath(fs.resolveHome(uri))
# TODO: what should we do here when there are multiple directories?
else if projectPath = @getPaths()[0]
path.normalize(fs.resolveHome(path.join(projectPath, uri)))
@defaultDirectoryProvider.normalizePath(fs.resolveHome(path.join(projectPath, uri)))
else
undefined
@ -360,9 +363,14 @@ class Project extends Model
else
@buildBuffer(absoluteFilePath)
shouldDestroyBufferOnFileDelete: ->
atom.config.get('core.closeDeletedFileTabs')
# Still needed when deserializing a tokenized buffer
buildBufferSync: (absoluteFilePath) ->
buffer = new TextBuffer({filePath: absoluteFilePath})
buffer = new TextBuffer({
filePath: absoluteFilePath
shouldDestroyOnFileDelete: @shouldDestroyBufferOnFileDelete})
@addBuffer(buffer)
buffer.loadSync()
buffer
@ -374,7 +382,9 @@ class Project extends Model
#
# Returns a {Promise} that resolves to the {TextBuffer}.
buildBuffer: (absoluteFilePath) ->
buffer = new TextBuffer({filePath: absoluteFilePath})
buffer = new TextBuffer({
filePath: absoluteFilePath
shouldDestroyOnFileDelete: @shouldDestroyBufferOnFileDelete})
@addBuffer(buffer)
buffer.load()
.then((buffer) -> buffer)

View File

@ -108,7 +108,10 @@ class TextEditorElement extends HTMLElement
buildModel: ->
@setModel(@workspace.buildTextEditor(
buffer: new TextBuffer(@textContent)
buffer: new TextBuffer({
text: @textContent
shouldDestroyOnFileDelete:
-> atom.config.get('core.closeDeletedFileTabs')})
softWrapped: false
tabLength: 2
softTabs: true

View File

@ -622,6 +622,18 @@ class TextEditorPresenter
return unless @scrollTop? and @lineHeight?
@startRow = Math.max(0, @lineTopIndex.rowForPixelPosition(@scrollTop))
atom.assert(
Number.isFinite(@startRow),
'Invalid start row',
(error) =>
error.metadata = {
startRow: @startRow?.toString(),
scrollTop: @scrollTop?.toString(),
scrollHeight: @scrollHeight?.toString(),
clientHeight: @clientHeight?.toString(),
lineHeight: @lineHeight?.toString()
}
)
updateEndRow: ->
return unless @scrollTop? and @lineHeight? and @height?

View File

@ -16,12 +16,11 @@ TextEditorElement = require './text-editor-element'
{isDoubleWidthCharacter, isHalfWidthCharacter, isKoreanCharacter, isWrapBoundary} = require './text-utils'
ZERO_WIDTH_NBSP = '\ufeff'
MAX_SCREEN_LINE_LENGTH = 500
# Essential: This class represents all essential editing state for a single
# {TextBuffer}, including cursor and selection positions, folds, and soft wraps.
# If you're manipulating the state of an editor, use this class. If you're
# interested in the visual appearance of editors, use {TextEditorElement}
# instead.
# If you're manipulating the state of an editor, use this class.
#
# A single {TextBuffer} can belong to multiple editors. For example, if the
# same file is open in two different panes, Atom creates a separate editor for
@ -162,7 +161,8 @@ class TextEditor extends Model
@softWrapAtPreferredLineLength ?= false
@preferredLineLength ?= 80
@buffer ?= new TextBuffer
@buffer ?= new TextBuffer({shouldDestroyOnFileDelete: ->
atom.config.get('core.closeDeletedFileTabs')})
@tokenizedBuffer ?= new TokenizedBuffer({
grammar, tabLength, @buffer, @largeFileMode, @assert
})
@ -193,8 +193,9 @@ class TextEditor extends Model
@displayLayer.setTextDecorationLayer(@tokenizedBuffer)
@defaultMarkerLayer = @displayLayer.addMarkerLayer()
@selectionsMarkerLayer ?= @addMarkerLayer(maintainHistory: true, persistent: true)
@selectionsMarkerLayer.trackDestructionInOnDidCreateMarkerCallbacks = true
@decorationManager = new DecorationManager(@displayLayer, @defaultMarkerLayer)
@decorationManager = new DecorationManager(@displayLayer)
@decorateMarkerLayer(@displayLayer.foldsMarkerLayer, {type: 'line-number', class: 'folded'})
for marker in @selectionsMarkerLayer.getMarkers()
@ -2965,7 +2966,7 @@ class TextEditor extends Model
else
@getEditorWidthInChars()
else
Infinity
MAX_SCREEN_LINE_LENGTH
###
Section: Indentation

View File

@ -8,6 +8,8 @@ ScopeDescriptor = require './scope-descriptor'
TokenizedBufferIterator = require './tokenized-buffer-iterator'
NullGrammar = require './null-grammar'
MAX_LINE_LENGTH_TO_TOKENIZE = 500
module.exports =
class TokenizedBuffer extends Model
grammar: null
@ -251,6 +253,8 @@ class TokenizedBuffer extends Model
buildTokenizedLineForRowWithText: (row, text, ruleStack = @stackForRow(row - 1), openScopes = @openScopesForRow(row)) ->
lineEnding = @buffer.lineEndingForRow(row)
if text.length > MAX_LINE_LENGTH_TO_TOKENIZE
text = text.slice(0, MAX_LINE_LENGTH_TO_TOKENIZE)
{tags, ruleStack} = @grammar.tokenizeLine(text, ruleStack, row is 0, false)
new TokenizedLine({openScopes, text, tags, ruleStack, lineEnding, @tokenIterator})

View File

@ -1,8 +0,0 @@
windowLoadSettings = null
exports.getWindowLoadSettings = ->
windowLoadSettings ?= JSON.parse(window.decodeURIComponent(window.location.hash.substr(1)))
exports.setWindowLoadSettings = (settings) ->
windowLoadSettings = settings
location.hash = encodeURIComponent(JSON.stringify(settings))

View File

@ -182,7 +182,7 @@ class Workspace extends Model
projectPath = _.find projectPaths, (projectPath) ->
itemPath is projectPath or itemPath?.startsWith(projectPath + path.sep)
itemTitle ?= "untitled"
projectPath ?= if itemPath then path.dirname(itemPath) else null
projectPath ?= if itemPath then path.dirname(itemPath) else projectPaths[0]
if projectPath?
projectPath = fs.tildify(projectPath)

View File

@ -1,7 +1,16 @@
{
"breakConfig": true,
"sourceMap": "inline",
"blacklist": ["es6.forOf", "useStrict"],
"optional": ["asyncToGenerator"],
"stage": 0
"plugins": [
["add-module-exports", {}],
["transform-async-to-generator", {}],
["transform-decorators-legacy", {}],
["transform-class-properties", {}],
["transform-es2015-modules-commonjs", {"strictMode": false}],
["transform-export-extensions", {}],
["transform-do-expressions", {}],
["transform-function-bind", {}],
["transform-object-rest-spread", {}],
["transform-flow-strip-types", {}],
["transform-react-jsx", {}]
]
}

View File

@ -2,9 +2,8 @@
var path = require('path')
var FileSystemBlobStore = require('../src/file-system-blob-store')
var NativeCompileCache = require('../src/native-compile-cache')
var getWindowLoadSettings = require('../src/get-window-load-settings')
var loadSettings = null
var loadSettingsError = null
var blobStore = null
window.onload = function () {
@ -25,20 +24,16 @@
// Normalize to make sure drive letter case is consistent on Windows
process.resourcesPath = path.normalize(process.resourcesPath)
if (loadSettingsError) {
throw loadSettingsError
}
var devMode = loadSettings.devMode || !loadSettings.resourcePath.startsWith(process.resourcesPath + path.sep)
var devMode = getWindowLoadSettings().devMode || !getWindowLoadSettings().resourcePath.startsWith(process.resourcesPath + path.sep)
if (devMode) {
setupDeprecatedPackages()
}
if (loadSettings.profileStartup) {
profileStartup(loadSettings, Date.now() - startTime)
if (getWindowLoadSettings().profileStartup) {
profileStartup(Date.now() - startTime)
} else {
setupWindow(loadSettings)
setupWindow()
setLoadTime(Date.now() - startTime)
}
} catch (error) {
@ -61,23 +56,23 @@
console.error(error.stack || error)
}
function setupWindow (loadSettings) {
function setupWindow () {
var CompileCache = require('../src/compile-cache')
CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME)
var ModuleCache = require('../src/module-cache')
ModuleCache.register(loadSettings)
ModuleCache.add(loadSettings.resourcePath)
ModuleCache.register(getWindowLoadSettings())
ModuleCache.add(getWindowLoadSettings().resourcePath)
// By explicitly passing the app version here, we could save the call
// of "require('remote').require('app').getVersion()".
var startCrashReporter = require('../src/crash-reporter-start')
startCrashReporter({_version: loadSettings.appVersion})
startCrashReporter({_version: getWindowLoadSettings().appVersion})
setupVmCompatibility()
setupCsonCache(CompileCache.getCacheDirectory())
var initialize = require(loadSettings.windowInitializationScript)
var initialize = require(getWindowLoadSettings().windowInitializationScript)
return initialize({blobStore: blobStore}).then(function () {
require('electron').ipcRenderer.send('window-command', 'window:loaded')
})
@ -105,11 +100,11 @@
}
}
function profileStartup (loadSettings, initialTime) {
function profileStartup (initialTime) {
function profile () {
console.profile('startup')
var startTime = Date.now()
setupWindow(loadSettings).then(function () {
setupWindow().then(function () {
setLoadTime(Date.now() - startTime + initialTime)
console.profileEnd('startup')
console.log('Switch to the Profiles tab to view the created startup profile')
@ -125,16 +120,6 @@
}
}
function parseLoadSettings () {
var rawLoadSettings = decodeURIComponent(window.location.hash.substr(1))
try {
loadSettings = JSON.parse(rawLoadSettings)
} catch (error) {
console.error('Failed to parse load settings: ' + rawLoadSettings)
loadSettingsError = error
}
}
var setupAtomHome = function () {
if (process.env.ATOM_HOME) {
return
@ -143,11 +128,10 @@
// Ensure ATOM_HOME is always set before anything else is required
// This is because of a difference in Linux not inherited between browser and render processes
// https://github.com/atom/atom/issues/5412
if (loadSettings && loadSettings.atomHome) {
process.env.ATOM_HOME = loadSettings.atomHome
if (getWindowLoadSettings() && getWindowLoadSettings().atomHome) {
process.env.ATOM_HOME = getWindowLoadSettings().atomHome
}
}
parseLoadSettings()
setupAtomHome()
})()

View File

@ -1,30 +1,32 @@
@import "ui-variables";
@title-bar-height: 22px;
@traffic-lights-width: 68px;
@inset-title-bar-height: 38px;
@inset-traffic-lights-width: 78px;
@title-bar-text-size: 13px;
@title-bar-height: 23px;
@title-bar-background-color: @base-background-color;
@title-bar-border-color: @base-border-color;
body.fullscreen .title-bar {
margin-top: -@title-bar-height;
}
// Title Bar -------------------------------
.title-bar {
height: @title-bar-height;
transition: margin-top 160ms;
flex-shrink: 0;
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
overflow: hidden;
box-sizing: content-box;
font-size: @title-bar-text-size;
background-color: @title-bar-background-color;
border-bottom: 1px solid @title-bar-border-color;
transition: margin-top 160ms;
-webkit-user-select: none;
-webkit-app-region: drag;
padding: 0 70px;
overflow: hidden;
.title {
flex: 0 1 auto;
overflow: hidden;
@ -32,10 +34,54 @@ body.fullscreen .title-bar {
text-overflow: ellipsis;
}
background-color: @title-bar-background-color;
border-bottom: 1px solid @title-bar-border-color;
.is-blurred & {
color: @text-color-subtle;
}
}
// Custom -------------------------------
.custom-title-bar {
.title-bar {
height: @title-bar-height;
padding-left: @traffic-lights-width;
padding-right: @traffic-lights-width;
}
&.fullscreen .title-bar {
margin-top: -@title-bar-height; // hide title bar in fullscreen mode
}
atom-panel.modal {
top: @title-bar-height; // Move modals down
}
}
// Custom Inset -------------------------------
.custom-inset-title-bar {
.title-bar {
height: @inset-title-bar-height;
padding-left: @inset-traffic-lights-width;
padding-right: @inset-traffic-lights-width;
}
&.fullscreen .title-bar {
margin-top: -@inset-title-bar-height; // hide title bar in fullscreen mode
}
atom-panel.modal {
top: @inset-title-bar-height; // Move modals down
}
}
// Hidden -------------------------------
.hidden-title-bar {
.status-bar {
-webkit-app-region: drag; // Enable dragging
}
}