diff --git a/.python-version b/.python-version index 49cdd668e..4712731cc 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -2.7.6 +2.7.12 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c8a79b4e0..ceb4186d9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: The following is a set of guidelines for contributing to Atom and its packages, which are hosted in the [Atom Organization](https://github.com/atom) on GitHub. -These are just guidelines, not rules, use your best judgment and feel free to propose changes to this document in a pull request. +These are just guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. #### Table Of Contents @@ -71,12 +71,12 @@ Here's a list of the big ones: There are many more, but this list should be a good starting point. For more information on how to work with Atom's official packages, see [Contributing to Atom Packages](https://github.com/atom/atom/blob/master/docs/contributing-to-packages.md). -Also, because Atom is so extensible, it's possible that a feature you've become accustomed to in Atom or an issue you're encountering aren't coming from a bundled package at all, but rather a [community package](https://atom.io/packages) you've installed. +Also, because Atom is so extensible, it's possible that a feature you've become accustomed to in Atom or an issue you're encountering isn't coming from a bundled package at all, but rather a [community package](https://atom.io/packages) you've installed. Each community package has its own repository too, and you should be able to find it in Settings > Packages for the packages you installed and contribute there. ### Design Decisions -When we make a significant decision in how we maintain the project and what we can or cannot support, we will be documenting them in the [atom/design-decisions repository](https://github.com/atom/design-decisions). If you have a question around how we do things, check to see if it is documented there. If it is *not* documented there, please open a new topic on [Discuss, the official Atom message board](https://discuss.atom.io) and ask your question. +When we make a significant decision in how we maintain the project and what we can or cannot support, we will document it in the [atom/design-decisions repository](https://github.com/atom/design-decisions). If you have a question around how we do things, check to see if it is documented there. If it is *not* documented there, please open a new topic on [Discuss, the official Atom message board](https://discuss.atom.io) and ask your question. ## How Can I Contribute? @@ -232,9 +232,7 @@ If you want to read about using Atom or developing packages in Atom, the [Atom F ### Pull Requests * Include screenshots and animated GIFs in your pull request whenever possible. -* Follow the [CoffeeScript](#coffeescript-styleguide), - [JavaScript](https://github.com/styleguide/javascript), - and [CSS](https://github.com/styleguide/css) styleguides. +* Follow the [JavaScript](#javascript-styleguide) and [CoffeeScript](#coffeescript-styleguide) styleguides. * Include thoughtfully-worded, well-structured [Jasmine](http://jasmine.github.io/) specs in the `./spec` folder. Run them using `apm test`. See the [Specs Styleguide](#specs-styleguide) below. * Document new code based on the diff --git a/LICENSE.md b/LICENSE.md index 4e47b02a2..4d231b456 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) 2016 GitHub Inc. +Copyright (c) 2014 GitHub Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index 4e40b6ead..bb6f6998e 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ the latest version of Atom. ### Debian Linux (Ubuntu) -Currently only a 64-bit version is available. +Atom is only available for 64-bit Linux systems. 1. Download `atom-amd64.deb` from the [Atom releases page](https://github.com/atom/atom/releases/latest). 2. Run `sudo dpkg --install atom-amd64.deb` on the downloaded package. @@ -56,7 +56,7 @@ repeat these steps to upgrade to future releases. ### Red Hat Linux (Fedora 21 and under, CentOS, Red Hat) -Currently only a 64-bit version is available. +Atom is only available for 64-bit Linux systems. 1. Download `atom.x86_64.rpm` from the [Atom releases page](https://github.com/atom/atom/releases/latest). 2. Run `sudo yum localinstall atom.x86_64.rpm` on the downloaded package. @@ -67,7 +67,7 @@ repeat these steps to upgrade to future releases. ### Fedora 22+ -Currently only a 64-bit version is available. +Atom is only available for 64-bit Linux systems. 1. Download `atom.x86_64.rpm` from the [Atom releases page](https://github.com/atom/atom/releases/latest). 2. Run `sudo dnf install ./atom.x86_64.rpm` on the downloaded package. @@ -83,8 +83,7 @@ An archive is available for people who don't want to install `atom` as root. This version enables you to install multiple Atom versions in parallel. It has been built on Ubuntu 64-bit, but should be compatible with other Linux distributions. -1. Install dependencies (on Ubuntu): `sudo apt install git gconf2 gconf-service libgtk2.0-0 libudev1 libgcrypt20 -libnotify4 libxtst6 libnss3 python gvfs-bin xdg-utils libcap2` +1. Install dependencies (on Ubuntu): `sudo apt install git gconf2 gconf-service libgtk2.0-0 libudev1 libgcrypt20 libnotify4 libxtst6 libnss3 python gvfs-bin xdg-utils libcap2` 2. Download `atom-amd64.tar.gz` from the [Atom releases page](https://github.com/atom/atom/releases/latest). 3. Run `tar xf atom-amd64.tar.gz` in the directory where you want to extract the Atom folder. 4. Launch Atom using the installed `atom` command from the newly extracted directory. diff --git a/atom.sh b/atom.sh index 0655e343e..a8c30fa19 100755 --- a/atom.sh +++ b/atom.sh @@ -28,7 +28,7 @@ while getopts ":wtfvh-:" opt; do REDIRECT_STDERR=1 EXPECT_OUTPUT=1 ;; - foreground|test) + foreground|benchmark|benchmark-test|test) EXPECT_OUTPUT=1 ;; esac diff --git a/benchmarks/benchmark-runner.js b/benchmarks/benchmark-runner.js new file mode 100644 index 000000000..30b23ffbf --- /dev/null +++ b/benchmarks/benchmark-runner.js @@ -0,0 +1,73 @@ +/** @babel */ + +import Chart from 'chart.js' +import glob from 'glob' +import fs from 'fs-plus' +import path from 'path' + +export default async function ({test, benchmarkPaths}) { + document.body.style.backgroundColor = '#ffffff' + document.body.style.overflow = 'auto' + + let paths = [] + for (const benchmarkPath of benchmarkPaths) { + if (fs.isDirectorySync(benchmarkPath)) { + paths = paths.concat(glob.sync(path.join(benchmarkPath, '**', '*.bench.js'))) + } else { + paths.push(benchmarkPath) + } + } + + while (paths.length > 0) { + const benchmark = require(paths.shift())({test}) + let results + if (benchmark instanceof Promise) { + results = await benchmark + } else { + results = benchmark + } + + const dataByBenchmarkName = {} + for (const {name, duration, x} of results) { + dataByBenchmarkName[name] = dataByBenchmarkName[name] || {points: []} + dataByBenchmarkName[name].points.push({x, y: duration}) + } + + const benchmarkContainer = document.createElement('div') + document.body.appendChild(benchmarkContainer) + for (const key in dataByBenchmarkName) { + const data = dataByBenchmarkName[key] + if (data.points.length > 1) { + const canvas = document.createElement('canvas') + benchmarkContainer.appendChild(canvas) + const chart = new Chart(canvas, { + type: 'line', + data: { + datasets: [{label: key, fill: false, data: data.points}] + }, + options: { + showLines: false, + scales: {xAxes: [{type: 'linear', position: 'bottom'}]} + } + }) + + const textualOutput = `${key}:\n\n` + data.points.map((p) => `${p.x}\t${p.y}`).join('\n') + console.log(textualOutput) + } else { + const title = document.createElement('h2') + title.textContent = key + benchmarkContainer.appendChild(title) + const duration = document.createElement('p') + duration.textContent = `${data.points[0].y}ms` + benchmarkContainer.appendChild(duration) + + const textualOutput = `${key}: ${data.points[0].y}` + console.log(textualOutput) + } + + global.atom.reset() + } + } + + return 0 +} diff --git a/benchmarks/text-editor-large-file-construction.bench.js b/benchmarks/text-editor-large-file-construction.bench.js new file mode 100644 index 000000000..694729724 --- /dev/null +++ b/benchmarks/text-editor-large-file-construction.bench.js @@ -0,0 +1,90 @@ +/** @babel */ + +import {TextEditor, TextBuffer} from 'atom' + +const MIN_SIZE_IN_KB = 0 * 1024 +const MAX_SIZE_IN_KB = 10 * 1024 +const SIZE_STEP_IN_KB = 1024 +const LINE_TEXT = 'Lorem ipsum dolor sit amet\n' +const TEXT = LINE_TEXT.repeat(Math.ceil(MAX_SIZE_IN_KB * 1024 / LINE_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() + + for (let pane of atom.workspace.getPanes()) { + pane.destroy() + } + + for (let sizeInKB = MIN_SIZE_IN_KB; sizeInKB < MAX_SIZE_IN_KB; sizeInKB += SIZE_STEP_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}) + atom.workspace.getActivePane().activateItem(editor) + let t1 = window.performance.now() + + data.push({ + name: 'Opening a large 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 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 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 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)) +} diff --git a/circle.yml b/circle.yml index 0054c4972..ee4eafc1f 100644 --- a/circle.yml +++ b/circle.yml @@ -7,9 +7,6 @@ machine: xcode: version: 7.3 - post: - - osascript -e 'tell application "System Events" to keystroke "x"' # clear screen saver - general: artifacts: - out/atom-mac.zip @@ -36,6 +33,7 @@ dependencies: test: override: - script/lint + - osascript -e 'tell application "System Events" to keystroke "x"' # clear screen saver - caffeinate -s script/test # Run with caffeinate to prevent screen saver experimental: diff --git a/docs/build-instructions/build-status.md b/docs/build-instructions/build-status.md index 57ee36e69..a17923224 100644 --- a/docs/build-instructions/build-status.md +++ b/docs/build-instructions/build-status.md @@ -37,7 +37,7 @@ | Incompatible Packages | [![macOS Build Status](https://travis-ci.org/atom/incompatible-packages.svg?branch=master)](https://travis-ci.org/atom/incompatible-packages) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/neet595s038x7w70/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/incompatible-packages/branch/master) | [![Dependency Status](https://david-dm.org/atom/incompatible-packages.svg)](https://david-dm.org/atom/incompatible-packages) | | Keybinding Resolver | [![macOS Build Status](https://travis-ci.org/atom/keybinding-resolver.svg?branch=master)](https://travis-ci.org/atom/keybinding-resolver) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/9jf31itx01hnn4nh/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/keybinding-resolver/branch/master) | [![Dependency Status](https://david-dm.org/atom/keybinding-resolver.svg)](https://david-dm.org/atom/keybinding-resolver) | | Line Ending Selector | [![macOS Build Status](https://travis-ci.org/atom/line-ending-selector.svg?branch=master)](https://travis-ci.org/atom/line-ending-selector) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/b3743n9ojomlpn1g/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/line-ending-selector/branch/master) | [![Dependency Status](https://david-dm.org/atom/line-ending-selector.svg)](https://david-dm.org/atom/line-ending-selector) | -| Link | [![macOS Build Status](https://travis-ci.org/atom/link.png?branch=master)](https://travis-ci.org/atom/link) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/1d3cb8ktd48k9vnl/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/link/branch/master) | [![Dependency Status](https://david-dm.org/atom/link.svg)](https://david-dm.org/atom/link) | +| Link | [![macOS Build Status](https://travis-ci.org/atom/link.svg?branch=master)](https://travis-ci.org/atom/link) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/1d3cb8ktd48k9vnl/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/link/branch/master) | [![Dependency Status](https://david-dm.org/atom/link.svg)](https://david-dm.org/atom/link) | | Markdown Preview | [![macOS Build Status](https://travis-ci.org/atom/markdown-preview.svg?branch=master)](https://travis-ci.org/atom/markdown-preview) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/bvh0evhh4v6w9b29/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/markdown-preview/branch/master) | [![Dependency Status](https://david-dm.org/atom/markdown-preview.svg)](https://david-dm.org/atom/markdown-preview) | | Metrics | [![macOS Build Status](https://travis-ci.org/atom/metrics.svg?branch=master)](https://travis-ci.org/atom/metrics) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/b5doi205xl3iex04/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/metrics/branch/master) | [![Dependency Status](https://david-dm.org/atom/metrics.svg)](https://david-dm.org/atom/metrics) | | Notifications | [![macOS Build Status](https://travis-ci.org/atom/notifications.svg?branch=master)](https://travis-ci.org/atom/notifications) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/ps3p8tj2okw57x0e/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/notifications/branch/master) | [![Dependency Status](https://david-dm.org/atom/notifications.svg)](https://david-dm.org/atom/notifications) | @@ -62,7 +62,7 @@ | Library | macOS | Windows | Dependencies | |---------|------|---------|--------------| -| Clear Cut | [![macOS Build Status](https://travis-ci.org/atom/clear-cut.png?branch=master)](https://travis-ci.org/atom/clear-cut) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/civ54x89l06286m9/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/clear-cut/branch/master) | [![Dependency Status](https://david-dm.org/atom/clear-cut.svg)](https://david-dm.org/atom/clear-cut) | +| Clear Cut | [![macOS Build Status](https://travis-ci.org/atom/clear-cut.svg?branch=master)](https://travis-ci.org/atom/clear-cut) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/civ54x89l06286m9/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/clear-cut/branch/master) | [![Dependency Status](https://david-dm.org/atom/clear-cut.svg)](https://david-dm.org/atom/clear-cut) | | Event Kit | [![macOS Build Status](https://travis-ci.org/atom/event-kit.svg?branch=master)](https://travis-ci.org/atom/event-kit) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/lb32q70204lpmlxo/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/event-kit/branch/master) | [![Dependency Status](https://david-dm.org/atom/event-kit.svg)](https://david-dm.org/atom/event-kit) | | Fs Plus | [![macOS Build Status](https://travis-ci.org/atom/fs-plus.svg?branch=master)](https://travis-ci.org/atom/fs-plus) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/gf2tleqp0hdek3o3/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/fs-plus/branch/master) | [![Dependency Status](https://david-dm.org/atom/fs-plus.svg)](https://david-dm.org/atom/fs-plus) | | Grim | [![macOS Build Status](https://travis-ci.org/atom/grim.svg)](https://travis-ci.org/atom/grim) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/i4m37pol77vygrvb/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/grim/branch/master) | [![Dependency Status](https://david-dm.org/atom/grim.svg)](https://david-dm.org/atom/grim) | @@ -107,6 +107,6 @@ | ShellScript | [![macOS Build Status](https://travis-ci.org/atom/language-shellscript.svg?branch=master)](https://travis-ci.org/atom/language-shellscript) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/p4um3lowgrg8y0ty/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-shellscript/branch/master) | | SQL | [![macOS Build Status](https://travis-ci.org/atom/language-sql.svg?branch=master)](https://travis-ci.org/atom/language-sql) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/ji31ouk5ehs4jdu1/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-sql/branch/master) | | TODO | [![macOS Build Status](https://travis-ci.org/atom/language-todo.svg?branch=master)](https://travis-ci.org/atom/language-todo) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/gcgb9m7h146lv6qp/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-todo/branch/master) | -| TOML | [![macOS Build Status](https://travis-ci.org/atom/language-toml.png?branch=master)](https://travis-ci.org/atom/language-toml) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/kohao3fjyk6xv0sc/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-toml/branch/master) | -| XML | [![macOS Build Status](https://travis-ci.org/atom/language-xml.png?branch=master)](https://travis-ci.org/atom/language-xml) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/m5f6rn74a6h3q5uq/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-xml/branch/master) | +| TOML | [![macOS Build Status](https://travis-ci.org/atom/language-toml.svg?branch=master)](https://travis-ci.org/atom/language-toml) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/kohao3fjyk6xv0sc/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-toml/branch/master) | +| XML | [![macOS Build Status](https://travis-ci.org/atom/language-xml.svg?branch=master)](https://travis-ci.org/atom/language-xml) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/m5f6rn74a6h3q5uq/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-xml/branch/master) | | YAML | [![macOS Build Status](https://travis-ci.org/atom/language-yaml.svg?branch=master)](https://travis-ci.org/atom/language-yaml) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/eaa4ql7kipgphc2n/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-yaml/branch/master) | diff --git a/docs/build-instructions/windows.md b/docs/build-instructions/windows.md index 4f5113c70..09be1b953 100644 --- a/docs/build-instructions/windows.md +++ b/docs/build-instructions/windows.md @@ -19,7 +19,7 @@ ## Instructions -You can run these commands using Command Prompt, PowerShell or Git Shell via [GitHub Desktop](https://desktop.github.com/). These instructions will assume the use of Command Prompt. +You can run these commands using Command Prompt, PowerShell, Git Shell, or any other terminal. These instructions will assume the use of Command Prompt. ``` cd C:\ @@ -36,28 +36,18 @@ To also install the newly built application, use `script\build --create-windows- * `--create-windows-installer`: creates an `.msi`, an `.exe` and a `.nupkg` installer in the `out/` directory. * `--install`: installs the application in `%LOCALAPPDATA%\Atom\app-dev\`. -## Do I have to use GitHub Desktop? - -No, you can use your existing Git! GitHub Desktop's Git Shell is just easier to set up. - -If you _prefer_ using your existing Git installation, make sure git's cmd directory is in your PATH env variable (e.g. `C:\Program Files (x86)\Git\cmd`) before you open your PowerShell or Command Prompt. - -It is also recommended you open your Command Prompt or PowerShell as Administrator. - -If none of this works, do install Github Desktop and use its Git Shell as it makes life easier. - ## Troubleshooting ### Common Errors * `node is not recognized` - * If you just installed Node.js, you'll need to restart your PowerShell/Command Prompt/Git Shell before the node - command is available on your Path. + * If you just installed Node.js, you'll need to restart Command Prompt before the `node` command is available on your Path. * `msbuild.exe failed with exit code: 1` - * Ensure you have Visual C++ support installed. Go into Add/Remove Programs, select Visual Studio and press Modify and then check the Visual C++ box. + * If you installed Visual Studio, ensure you have Visual C++ support installed. Go into Add/Remove Programs, select Visual Studio, press Modify, and then check the Visual C++ box. + * If you installed Visual C++ Build Tools, ensure you have Windows 8 SDK support installed. Go into Add/Remove Programs, select Visual Studio, press Modify and then check the Windows 8 SDK box. * `script\build` stop with no error or warning shortly after displaying the versions of node, npm and Python - * Make sure that the path where you have checked out Atom does not include a space. e.g. use `c:\atom` and not `c:\my stuff\atom` + * Make sure that the path where you have checked out Atom does not include a space. For example, use `C:\atom` instead of `C:\my stuff\atom`. * `script\build` outputs only the Node.js and Python versions before returning * Try moving the repository to `C:\atom`. Most likely, the path is too long. @@ -77,7 +67,7 @@ If none of this works, do install Github Desktop and use its Git Shell as it mak * See the next item. * `error MSB8020: The build tools for Visual Studio 201? (Platform Toolset = 'v1?0') cannot be found.` - * Try setting the `GYP_MSVS_VERSION` environment variable to 2013 or 2015 depending on what version of Visual Studio you are running and then `script\clean` followed by `script\build` (re-open your command prompt or Powershell window if you set it using the GUI) + * Try setting the `GYP_MSVS_VERSION` environment variable to 2013 or 2015 depending on what version of Visual Studio/Build Tools is installed and then `script\clean` followed by `script\build` (re-open the Command Prompt if you set the variable using the GUI). * `'node-gyp' is not recognized as an internal or external command, operable program or batch file.` * Try running `npm install -g node-gyp`, and run `script\build` again. diff --git a/exports/atom.js b/exports/atom.js index b22a4cdfb..9ad4f60c2 100644 --- a/exports/atom.js +++ b/exports/atom.js @@ -3,8 +3,6 @@ import TextBuffer, {Point, Range} from 'text-buffer' import {File, Directory} from 'pathwatcher' import {Emitter, Disposable, CompositeDisposable} from 'event-kit' -import Grim from 'grim' -import dedent from 'dedent' import BufferedNodeProcess from '../src/buffered-node-process' import BufferedProcess from '../src/buffered-process' import GitRepository from '../src/git-repository' @@ -39,25 +37,7 @@ if (process.platform === 'win32') { // only be exported when not running as a child node process if (process.type === 'renderer') { atomExport.Task = require('../src/task') - - const TextEditor = (params) => { - return atom.workspace.buildTextEditor(params) - } - - TextEditor.prototype = require('../src/text-editor').prototype - - Object.defineProperty(atomExport, 'TextEditor', { - enumerable: true, - get () { - Grim.deprecate(dedent` - The \`TextEditor\` constructor is no longer public. - - To construct a text editor, use \`atom.workspace.buildTextEditor()\`. - To check if an object is a text editor, use \`atom.workspace.isTextEditor(object)\`. - `) - return TextEditor - } - }) + atomExport.TextEditor = require('../src/text-editor') } export default atomExport diff --git a/keymaps/darwin.cson b/keymaps/darwin.cson index 953fb0c48..4ae6d88db 100644 --- a/keymaps/darwin.cson +++ b/keymaps/darwin.cson @@ -133,6 +133,20 @@ 'cmd-ctrl-left': 'editor:move-selection-left' 'cmd-ctrl-right': 'editor:move-selection-right' + # Emacs + 'alt-f': 'editor:move-to-end-of-word' + 'alt-ctrl-f': 'editor:move-to-next-subword-boundary' + 'alt-F': 'editor:select-to-end-of-word' + 'alt-ctrl-F': 'editor:select-to-next-subword-boundary' + 'alt-b': 'editor:move-to-beginning-of-word' + 'alt-ctrl-b': 'editor:move-to-previous-subword-boundary' + 'alt-B': 'editor:select-to-beginning-of-word' + 'alt-ctrl-B': 'editor:select-to-previous-subword-boundary' + 'alt-h': 'editor:delete-to-beginning-of-word' + 'alt-ctrl-h': 'editor:delete-to-beginning-of-subword' + 'alt-d': 'editor:delete-to-end-of-word' + 'alt-ctrl-d': 'editor:delete-to-end-of-subword' + # Sublime Parity 'cmd-a': 'core:select-all' 'cmd-alt-p': 'editor:log-cursor-scope' diff --git a/keymaps/emacs.cson b/keymaps/emacs.cson deleted file mode 100644 index 764c97938..000000000 --- a/keymaps/emacs.cson +++ /dev/null @@ -1,13 +0,0 @@ -'atom-text-editor': - 'alt-f': 'editor:move-to-end-of-word' - 'alt-ctrl-f': 'editor:move-to-next-subword-boundary' - 'alt-F': 'editor:select-to-end-of-word' - 'alt-ctrl-F': 'editor:select-to-next-subword-boundary' - 'alt-b': 'editor:move-to-beginning-of-word' - 'alt-ctrl-b': 'editor:move-to-previous-subword-boundary' - 'alt-B': 'editor:select-to-beginning-of-word' - 'alt-ctrl-B': 'editor:select-to-previous-subword-boundary' - 'alt-h': 'editor:delete-to-beginning-of-word' - 'alt-ctrl-h': 'editor:delete-to-beginning-of-subword' - 'alt-d': 'editor:delete-to-end-of-word' - 'alt-ctrl-d': 'editor:delete-to-end-of-subword' diff --git a/keymaps/win32.cson b/keymaps/win32.cson index 10450ec18..d43c124d4 100644 --- a/keymaps/win32.cson +++ b/keymaps/win32.cson @@ -13,7 +13,7 @@ 'left': 'core:move-left' 'right': 'core:move-right' 'ctrl-alt-r': 'window:reload' - 'ctrl-alt-i': 'window:toggle-dev-tools' + 'ctrl-shift-i': 'window:toggle-dev-tools' 'ctrl-alt-p': 'window:run-package-specs' 'ctrl-shift-o': 'application:open-folder' 'ctrl-alt-o': 'application:add-project-folder' diff --git a/menus/darwin.cson b/menus/darwin.cson index ccbb5f7a6..b967220c0 100644 --- a/menus/darwin.cson +++ b/menus/darwin.cson @@ -147,6 +147,7 @@ { label: 'Open In Dev Mode…', command: 'application:open-dev' } { label: 'Reload Window', command: 'window:reload' } { label: 'Run Package Specs', command: 'window:run-package-specs' } + { label: 'Run Benchmarks', command: 'window:run-benchmarks' } { label: 'Toggle Developer Tools', command: 'window:toggle-dev-tools' } ] } diff --git a/package.json b/package.json index 60d7521df..07186c114 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "1.12.0-dev", + "version": "1.13.0-dev", "description": "A hackable text editor for the 21st Century.", "main": "./src/main-process/main.js", "repository": { @@ -15,23 +15,25 @@ "electronVersion": "1.3.6", "dependencies": { "async": "0.2.6", - "atom-keymap": "6.3.5", + "atom-keymap": "7.0.5", "atom-ui": "0.4.1", "babel-core": "5.8.38", "cached-run-in-this-context": "0.4.1", "chai": "3.5.0", + "chart.js": "^2.3.0", "clear-cut": "^2.0.1", - "coffee-script": "1.8.0", + "coffee-script": "1.11.1", "color": "^0.7.3", "dedent": "^0.6.0", - "devtron": "1.1.0", + "devtron": "1.3.0", "event-kit": "^2.1.0", "find-parent-dir": "^0.3.0", - "first-mate": "6.0.0", - "fs-plus": "2.9.1", + "first-mate": "6.1.0", + "fs-plus": "2.9.2", "fstream": "0.1.24", "fuzzaldrin": "^2.1", "git-utils": "^4.1.2", + "glob": "^7.1.1", "grim": "1.5.0", "jasmine-json": "~0.0", "jasmine-tagged": "^1.1.4", @@ -52,13 +54,13 @@ "scandal": "^2.2.1", "scoped-property-store": "^0.17.0", "scrollbar-style": "^3.2", - "season": "^5.3", + "season": "^5.4.1", "semver": "^4.3.3", - "service-hub": "^0.7.0", + "service-hub": "^0.7.2", "sinon": "1.17.4", "source-map-support": "^0.3.2", "temp": "0.8.1", - "text-buffer": "9.2.12", + "text-buffer": "9.4.0", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "winreg": "^1.2.1", @@ -71,89 +73,89 @@ "atom-light-ui": "0.45.0", "base16-tomorrow-dark-theme": "1.3.0", "base16-tomorrow-light-theme": "1.3.0", - "one-dark-ui": "1.6.1", - "one-light-ui": "1.6.1", - "one-dark-syntax": "1.4.0", - "one-light-syntax": "1.4.0", - "solarized-dark-syntax": "1.0.3", - "solarized-light-syntax": "1.0.3", + "one-dark-ui": "1.6.2", + "one-light-ui": "1.6.2", + "one-dark-syntax": "1.5.0", + "one-light-syntax": "1.5.0", + "solarized-dark-syntax": "1.0.5", + "solarized-light-syntax": "1.0.5", "about": "1.7.0", - "archive-view": "0.61.1", + "archive-view": "0.62.0", "autocomplete-atom-api": "0.10.0", - "autocomplete-css": "0.11.2", + "autocomplete-css": "0.13.1", "autocomplete-html": "0.7.2", - "autocomplete-plus": "2.31.3", + "autocomplete-plus": "2.32.0", "autocomplete-snippets": "1.11.0", "autoflow": "0.27.0", "autosave": "0.23.1", "background-tips": "0.26.1", "bookmarks": "0.42.0", "bracket-matcher": "0.82.2", - "command-palette": "0.38.0", + "command-palette": "0.39.0", "deprecation-cop": "0.54.1", "dev-live-reload": "0.47.0", "encoding-selector": "0.22.0", "exception-reporting": "0.40.0", - "find-and-replace": "0.201.1", + "find-and-replace": "0.202.1", "fuzzy-finder": "1.4.0", "git-diff": "1.1.0", "go-to-line": "0.31.0", "grammar-selector": "0.48.2", - "image-view": "0.59.0", + "image-view": "0.60.0", "incompatible-packages": "0.26.1", "keybinding-resolver": "0.35.0", "line-ending-selector": "0.5.0", "link": "0.31.2", - "markdown-preview": "0.158.7", + "markdown-preview": "0.158.8", "metrics": "1.0.0", "notifications": "0.65.1", "open-on-github": "1.2.1", "package-generator": "1.0.1", - "settings-view": "0.243.0", + "settings-view": "0.243.1", "snippets": "1.0.3", - "spell-check": "0.68.2", - "status-bar": "1.4.1", + "spell-check": "0.68.4", + "status-bar": "1.6.0", "styleguide": "0.47.2", "symbols-view": "0.113.1", - "tabs": "0.101.1", + "tabs": "0.103.0", "timecop": "0.33.2", - "tree-view": "0.209.3", + "tree-view": "0.210.0", "update-package-dependencies": "0.10.0", "welcome": "0.35.1", - "whitespace": "0.33.0", + "whitespace": "0.35.0", "wrap-guide": "0.38.2", - "language-c": "0.53.1", - "language-clojure": "0.21.0", - "language-coffee-script": "0.47.2", + "language-c": "0.54.0", + "language-clojure": "0.22.1", + "language-coffee-script": "0.48.0", "language-csharp": "0.12.1", - "language-css": "0.39.0", + "language-css": "0.40.1", "language-gfm": "0.88.0", "language-git": "0.15.0", - "language-go": "0.42.1", - "language-html": "0.45.1", - "language-hyperlink": "0.16.0", + "language-go": "0.43.0", + "language-html": "0.46.1", + "language-hyperlink": "0.16.1", "language-java": "0.24.0", - "language-javascript": "0.120.0", - "language-json": "0.18.2", - "language-less": "0.29.5", + "language-javascript": "0.122.0", + "language-json": "0.18.3", + "language-less": "0.29.6", "language-make": "0.22.2", "language-mustache": "0.13.0", "language-objective-c": "0.15.1", - "language-perl": "0.36.0", - "language-php": "0.37.2", + "language-perl": "0.37.0", + "language-php": "0.37.3", "language-property-list": "0.8.0", - "language-python": "0.45.0", - "language-ruby": "0.70.1", + "language-python": "0.45.1", + "language-ruby": "0.70.2", "language-ruby-on-rails": "0.25.1", - "language-sass": "0.56.0", - "language-shellscript": "0.22.5", + "language-sass": "0.57.0", + "language-shellscript": "0.23.0", "language-source": "0.9.0", - "language-sql": "0.24.0", + "language-sql": "0.25.0", "language-text": "0.7.1", - "language-todo": "0.29.0", - "language-toml": "0.18.0", - "language-xml": "0.34.10", - "language-yaml": "0.27.0" + "language-todo": "0.29.1", + "language-toml": "0.18.1", + "language-xml": "0.34.12", + "language-yaml": "0.27.1" }, "private": true, "scripts": { @@ -161,8 +163,6 @@ "test": "node script/test" }, "standard": { - "ignore": [], - "parser": "babel-eslint", "globals": [ "atom", "afterEach", diff --git a/resources/app-icons/beta/png/1024.png b/resources/app-icons/beta/png/1024.png index b3d712d4a..3a25d4605 100644 Binary files a/resources/app-icons/beta/png/1024.png and b/resources/app-icons/beta/png/1024.png differ diff --git a/resources/app-icons/beta/png/128.png b/resources/app-icons/beta/png/128.png index e67a95a7a..8b5916694 100644 Binary files a/resources/app-icons/beta/png/128.png and b/resources/app-icons/beta/png/128.png differ diff --git a/resources/app-icons/beta/png/16.png b/resources/app-icons/beta/png/16.png index c7a0c0d72..c004e3afb 100644 Binary files a/resources/app-icons/beta/png/16.png and b/resources/app-icons/beta/png/16.png differ diff --git a/resources/app-icons/beta/png/24.png b/resources/app-icons/beta/png/24.png index dd8dcf22a..f58d0508d 100644 Binary files a/resources/app-icons/beta/png/24.png and b/resources/app-icons/beta/png/24.png differ diff --git a/resources/app-icons/beta/png/256.png b/resources/app-icons/beta/png/256.png index b48fbf1ae..293ffa281 100644 Binary files a/resources/app-icons/beta/png/256.png and b/resources/app-icons/beta/png/256.png differ diff --git a/resources/app-icons/beta/png/32.png b/resources/app-icons/beta/png/32.png index 238539f47..ffcbe6b50 100644 Binary files a/resources/app-icons/beta/png/32.png and b/resources/app-icons/beta/png/32.png differ diff --git a/resources/app-icons/beta/png/48.png b/resources/app-icons/beta/png/48.png index 17b155db6..8da49e368 100644 Binary files a/resources/app-icons/beta/png/48.png and b/resources/app-icons/beta/png/48.png differ diff --git a/resources/app-icons/beta/png/512.png b/resources/app-icons/beta/png/512.png index 1fa42ab4f..28bd1c00e 100644 Binary files a/resources/app-icons/beta/png/512.png and b/resources/app-icons/beta/png/512.png differ diff --git a/resources/app-icons/beta/png/64.png b/resources/app-icons/beta/png/64.png index 19d27487d..05d128905 100644 Binary files a/resources/app-icons/beta/png/64.png and b/resources/app-icons/beta/png/64.png differ diff --git a/resources/app-icons/dev/png/1024.png b/resources/app-icons/dev/png/1024.png index 36dcbf3a7..02d212c64 100644 Binary files a/resources/app-icons/dev/png/1024.png and b/resources/app-icons/dev/png/1024.png differ diff --git a/resources/app-icons/dev/png/128.png b/resources/app-icons/dev/png/128.png index c0b33e630..c1d0441af 100644 Binary files a/resources/app-icons/dev/png/128.png and b/resources/app-icons/dev/png/128.png differ diff --git a/resources/app-icons/dev/png/16.png b/resources/app-icons/dev/png/16.png index 8466559c2..fdb9d1c41 100644 Binary files a/resources/app-icons/dev/png/16.png and b/resources/app-icons/dev/png/16.png differ diff --git a/resources/app-icons/dev/png/24.png b/resources/app-icons/dev/png/24.png index 55e6f2da7..cb7bff8fe 100644 Binary files a/resources/app-icons/dev/png/24.png and b/resources/app-icons/dev/png/24.png differ diff --git a/resources/app-icons/dev/png/256.png b/resources/app-icons/dev/png/256.png index 3faf4d2c5..0886d461a 100644 Binary files a/resources/app-icons/dev/png/256.png and b/resources/app-icons/dev/png/256.png differ diff --git a/resources/app-icons/dev/png/32.png b/resources/app-icons/dev/png/32.png index 5f3a0e68c..586f35aa4 100644 Binary files a/resources/app-icons/dev/png/32.png and b/resources/app-icons/dev/png/32.png differ diff --git a/resources/app-icons/dev/png/48.png b/resources/app-icons/dev/png/48.png index adbbcf751..06c3ce2f8 100644 Binary files a/resources/app-icons/dev/png/48.png and b/resources/app-icons/dev/png/48.png differ diff --git a/resources/app-icons/dev/png/512.png b/resources/app-icons/dev/png/512.png index c03c309fe..8109a009d 100644 Binary files a/resources/app-icons/dev/png/512.png and b/resources/app-icons/dev/png/512.png differ diff --git a/resources/app-icons/dev/png/64.png b/resources/app-icons/dev/png/64.png index 8aba24648..ff59c7be0 100644 Binary files a/resources/app-icons/dev/png/64.png and b/resources/app-icons/dev/png/64.png differ diff --git a/resources/app-icons/stable/png/1024.png b/resources/app-icons/stable/png/1024.png index a09f04805..64a689a04 100644 Binary files a/resources/app-icons/stable/png/1024.png and b/resources/app-icons/stable/png/1024.png differ diff --git a/resources/app-icons/stable/png/128.png b/resources/app-icons/stable/png/128.png index 78948fb24..ff9c64e6e 100644 Binary files a/resources/app-icons/stable/png/128.png and b/resources/app-icons/stable/png/128.png differ diff --git a/resources/app-icons/stable/png/16.png b/resources/app-icons/stable/png/16.png index 52df06fa2..5f8762668 100644 Binary files a/resources/app-icons/stable/png/16.png and b/resources/app-icons/stable/png/16.png differ diff --git a/resources/app-icons/stable/png/24.png b/resources/app-icons/stable/png/24.png index 0bf12cbb0..a1216ab9d 100644 Binary files a/resources/app-icons/stable/png/24.png and b/resources/app-icons/stable/png/24.png differ diff --git a/resources/app-icons/stable/png/256.png b/resources/app-icons/stable/png/256.png index a98caeb7b..ef5b45234 100644 Binary files a/resources/app-icons/stable/png/256.png and b/resources/app-icons/stable/png/256.png differ diff --git a/resources/app-icons/stable/png/32.png b/resources/app-icons/stable/png/32.png index a960acf00..f2dc3b46a 100644 Binary files a/resources/app-icons/stable/png/32.png and b/resources/app-icons/stable/png/32.png differ diff --git a/resources/app-icons/stable/png/48.png b/resources/app-icons/stable/png/48.png index 196717e27..6020091f7 100644 Binary files a/resources/app-icons/stable/png/48.png and b/resources/app-icons/stable/png/48.png differ diff --git a/resources/app-icons/stable/png/512.png b/resources/app-icons/stable/png/512.png index f1d35d951..cd24c9994 100644 Binary files a/resources/app-icons/stable/png/512.png and b/resources/app-icons/stable/png/512.png differ diff --git a/resources/app-icons/stable/png/64.png b/resources/app-icons/stable/png/64.png index b9fcf78ab..aa1c6752e 100644 Binary files a/resources/app-icons/stable/png/64.png and b/resources/app-icons/stable/png/64.png differ diff --git a/resources/win/atom.cmd b/resources/win/atom.cmd index 73c4ddb01..43ec8ebe3 100644 --- a/resources/win/atom.cmd +++ b/resources/win/atom.cmd @@ -5,14 +5,16 @@ SET WAIT= SET PSARGS=%* FOR %%a IN (%*) DO ( - IF /I "%%a"=="-f" SET EXPECT_OUTPUT=YES - IF /I "%%a"=="--foreground" SET EXPECT_OUTPUT=YES - IF /I "%%a"=="-h" SET EXPECT_OUTPUT=YES - IF /I "%%a"=="--help" SET EXPECT_OUTPUT=YES - IF /I "%%a"=="-t" SET EXPECT_OUTPUT=YES - IF /I "%%a"=="--test" SET EXPECT_OUTPUT=YES - IF /I "%%a"=="-v" SET EXPECT_OUTPUT=YES - IF /I "%%a"=="--version" SET EXPECT_OUTPUT=YES + IF /I "%%a"=="-f" SET EXPECT_OUTPUT=YES + IF /I "%%a"=="--foreground" SET EXPECT_OUTPUT=YES + IF /I "%%a"=="-h" SET EXPECT_OUTPUT=YES + IF /I "%%a"=="--help" SET EXPECT_OUTPUT=YES + IF /I "%%a"=="-t" SET EXPECT_OUTPUT=YES + IF /I "%%a"=="--test" SET EXPECT_OUTPUT=YES + IF /I "%%a"=="--benchmark" SET EXPECT_OUTPUT=YES + IF /I "%%a"=="--benchmark-test" SET EXPECT_OUTPUT=YES + IF /I "%%a"=="-v" SET EXPECT_OUTPUT=YES + IF /I "%%a"=="--version" SET EXPECT_OUTPUT=YES IF /I "%%a"=="-w" ( SET EXPECT_OUTPUT=YES SET WAIT=YES diff --git a/script/build b/script/build index beab4088a..680666b05 100755 --- a/script/build +++ b/script/build @@ -10,7 +10,8 @@ require('./bootstrap') require('coffee-script/register') require('colors') -const argv = require('yargs') +const yargs = require('yargs') +const argv = yargs .usage('Usage: $0 [options]') .help('help') .describe('code-sign', 'Code-sign executables (macOS and Windows only)') @@ -19,6 +20,7 @@ const argv = require('yargs') .describe('create-rpm-package', 'Create .rpm package (Linux only)') .describe('compress-artifacts', 'Compress Atom binaries (and symbols on macOS)') .describe('install', 'Install Atom') + .wrap(yargs.terminalWidth()) .argv const cleanOutputDirectory = require('./lib/clean-output-directory') diff --git a/script/lib/copy-assets.js b/script/lib/copy-assets.js index 3c33ad13a..28357ee9a 100644 --- a/script/lib/copy-assets.js +++ b/script/lib/copy-assets.js @@ -12,6 +12,7 @@ const includePathInPackagedApp = require('./include-path-in-packaged-app') module.exports = function () { console.log(`Copying assets to ${CONFIG.intermediateAppPath}`); let srcPaths = [ + path.join(CONFIG.repositoryRootPath, 'benchmarks', 'benchmark-runner.js'), path.join(CONFIG.repositoryRootPath, 'dot-atom'), path.join(CONFIG.repositoryRootPath, 'exports'), path.join(CONFIG.repositoryRootPath, 'node_modules'), diff --git a/script/lib/create-windows-installer.js b/script/lib/create-windows-installer.js index ac71c0da3..fb22dd085 100644 --- a/script/lib/create-windows-installer.js +++ b/script/lib/create-windows-installer.js @@ -6,6 +6,7 @@ const fs = require('fs-extra') const glob = require('glob') const os = require('os') const path = require('path') +const spawnSync = require('./spawn-sync') const CONFIG = require('../config') @@ -21,7 +22,8 @@ module.exports = function (packagedAppPath, codeSign) { } const certPath = path.join(os.tmpdir(), 'win.p12') - if (codeSign && process.env.WIN_P12KEY_URL) { + const signing = codeSign && process.env.WIN_P12KEY_URL + if (signing) { downloadFileFromGithub(process.env.WIN_P12KEY_URL, certPath) options.certificateFile = certPath options.certificatePassword = process.env.WIN_P12KEY_PASSWORD @@ -42,9 +44,30 @@ module.exports = function (packagedAppPath, codeSign) { } } } + + // Squirrel signs its own copy of the executables but we need them for the portable ZIP + const extractSignedExes = function() { + if (signing) { + for (let nupkgPath of glob.sync(`${CONFIG.buildOutputPath}/*-full.nupkg`)) { + if (nupkgPath.includes(CONFIG.appMetadata.version)) { + console.log(`Extracting signed executables from ${nupkgPath} for use in portable zip`) + var atomOutPath = path.join(path.dirname(packagedAppPath), 'Atom') + spawnSync('7z.exe', ['e', nupkgPath, 'lib\\net45\\*.exe', '-aoa'], {cwd: atomOutPath}) + spawnSync(process.env.COMSPEC, ['/c', `move /y ${path.join(atomOutPath, 'squirrel.exe')} ${path.join(atomOutPath, 'update.exe')}`]) + return + } + } + } + } + console.log(`Creating Windows Installer for ${packagedAppPath}`) - return electronInstaller.createWindowsInstaller(options).then(cleanUp, function (error) { - console.log(`Windows installer creation failed:\n${error}`) - cleanUp() - }) + return electronInstaller.createWindowsInstaller(options) + .then(extractSignedExes, function (error) { + console.log(`Extracting signed executables failed:\n${error}`) + cleanUp() + }) + .then(cleanUp, function (error) { + console.log(`Windows installer creation failed:\n${error}`) + cleanUp() + }) } diff --git a/script/lib/transpile-babel-paths.js b/script/lib/transpile-babel-paths.js index 0a987e124..3c440a4bd 100644 --- a/script/lib/transpile-babel-paths.js +++ b/script/lib/transpile-babel-paths.js @@ -25,6 +25,7 @@ module.exports = function () { function getPathsToTranspile () { let paths = [] + paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'benchmarks', '**', '*.js'))) paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'exports', '**', '*.js'))) paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'src', '**', '*.js'))) for (let packageName of Object.keys(CONFIG.appMetadata.packageDependencies)) { diff --git a/script/package.json b/script/package.json index 0ddc54928..1f4cf782f 100644 --- a/script/package.json +++ b/script/package.json @@ -4,7 +4,6 @@ "dependencies": { "async": "2.0.1", "babel-core": "5.8.38", - "babel-eslint": "6.1.2", "coffeelint": "1.15.7", "colors": "1.1.2", "csslint": "1.0.2", @@ -24,7 +23,7 @@ "runas": "3.1.1", "season": "5.3.0", "semver": "5.3.0", - "standard": "6.0.0", + "standard": "8.4.0", "sync-request": "3.0.1", "tello": "1.0.5", "webdriverio": "2.4.5", diff --git a/script/test b/script/test index ccb5dce86..38568207f 100755 --- a/script/test +++ b/script/test @@ -83,9 +83,19 @@ for (let packageName in CONFIG.appMetadata.packageDependencies) { }) } +function runBenchmarkTests (callback) { + const benchmarksPath = path.join(CONFIG.repositoryRootPath, 'benchmarks') + const testArguments = ['--benchmark-test', benchmarksPath] + + console.log('Executing benchmark tests'.bold.green) + const cp = childProcess.spawn(executablePath, testArguments, {stdio: 'inherit'}) + cp.on('error', error => { callback(error) }) + cp.on('close', exitCode => { callback(null, exitCode) }) +} + let testSuitesToRun if (process.platform === 'darwin') { - testSuitesToRun = [runCoreMainProcessTests, runCoreRenderProcessTests].concat(packageTestSuites) + testSuitesToRun = [runCoreMainProcessTests, runCoreRenderProcessTests, runBenchmarkTests].concat(packageTestSuites) } else { testSuitesToRun = [runCoreMainProcessTests] } diff --git a/spec/async-spec-helpers.js b/spec/async-spec-helpers.js index 96abb2818..115f944b5 100644 --- a/spec/async-spec-helpers.js +++ b/spec/async-spec-helpers.js @@ -29,23 +29,20 @@ export function afterEach (fn) { } }) -export function conditionPromise (condition) { - const timeoutError = new Error("Timed out waiting on condition") - Error.captureStackTrace(timeoutError, conditionPromise) +export async function conditionPromise (condition) { + const startTime = Date.now() - return new Promise(function (resolve, reject) { - const interval = global.setInterval(function () { - if (condition()) { - global.clearInterval(interval) - global.clearTimeout(timeout) - resolve() - } - }, 100) - const timeout = global.setTimeout(function () { - global.clearInterval(interval) - reject(timeoutError) - }, 5000) - }) + while (true) { + await timeoutPromise(100) + + if (await condition()) { + return + } + + if (Date.now() - startTime > 5000) { + throw new Error("Timed out waiting on condition") + } + } } export function timeoutPromise (timeout) { diff --git a/spec/atom-environment-spec.coffee b/spec/atom-environment-spec.coffee index f3cb83bf0..6715d04e2 100644 --- a/spec/atom-environment-spec.coffee +++ b/spec/atom-environment-spec.coffee @@ -1,8 +1,6 @@ _ = require 'underscore-plus' path = require 'path' temp = require 'temp' -Package = require '../src/package' -ThemeManager = require '../src/theme-manager' AtomEnvironment = require '../src/atom-environment' StorageFolder = require '../src/storage-folder' @@ -392,8 +390,9 @@ describe "AtomEnvironment", -> describe "when the opened path is a uri", -> it "adds it to the project's paths as is", -> pathToOpen = 'remote://server:7644/some/dir/path' + spyOn(atom.project, 'addPath') atom.openLocations([{pathToOpen}]) - expect(atom.project.getPaths()[0]).toBe pathToOpen + expect(atom.project.addPath).toHaveBeenCalledWith(pathToOpen) describe "::updateAvailable(info) (called via IPC from browser process)", -> subscription = null diff --git a/spec/atom-portable-spec.coffee b/spec/atom-portable-spec.coffee index 8f45f314e..aeb71b7c1 100644 --- a/spec/atom-portable-spec.coffee +++ b/spec/atom-portable-spec.coffee @@ -1,6 +1,5 @@ path = require 'path' fs = require 'fs-plus' -temp = require 'temp' AtomPortable = require '../src/main-process/atom-portable' portableModeCommonPlatformBehavior = (platform) -> diff --git a/spec/atom-reporter.coffee b/spec/atom-reporter.coffee index dfb522f5f..ef46b09c5 100644 --- a/spec/atom-reporter.coffee +++ b/spec/atom-reporter.coffee @@ -3,6 +3,7 @@ _ = require 'underscore-plus' grim = require 'grim' marked = require 'marked' listen = require '../src/delegated-listener' +ipcHelpers = require '../src/ipc-helpers' formatStackTrace = (spec, message='', stackTrace) -> return stackTrace unless stackTrace @@ -173,7 +174,7 @@ class AtomReporter listen document, 'click', '.stack-trace', (event) -> event.currentTarget.classList.toggle('expanded') - @reloadButton.addEventListener('click', -> require('electron').ipcRenderer.send('call-window-method', 'reload')) + @reloadButton.addEventListener('click', -> ipcHelpers.call('window-method', 'reload')) updateSpecCounts: -> if @skippedCount diff --git a/spec/compile-cache-spec.coffee b/spec/compile-cache-spec.coffee index c17ba01e1..bec689c7d 100644 --- a/spec/compile-cache-spec.coffee +++ b/spec/compile-cache-spec.coffee @@ -17,7 +17,7 @@ describe 'CompileCache', -> CompileCache.resetCacheStats() spyOn(Babel, 'transform').andReturn {code: 'the-babel-code'} - spyOn(CoffeeScript, 'compile').andReturn {js: 'the-coffee-code', v3SourceMap: "{}"} + spyOn(CoffeeScript, 'compile').andReturn 'the-coffee-code' spyOn(TypeScriptSimple::, 'compile').andReturn 'the-typescript-code' afterEach -> diff --git a/spec/decoration-manager-spec.coffee b/spec/decoration-manager-spec.coffee index c428df8cf..44da440d9 100644 --- a/spec/decoration-manager-spec.coffee +++ b/spec/decoration-manager-spec.coffee @@ -1,5 +1,4 @@ DecorationManager = require '../src/decoration-manager' -_ = require 'underscore-plus' describe "DecorationManager", -> [decorationManager, buffer, defaultMarkerLayer] = [] diff --git a/spec/fixtures/packages/package-with-directory-provider/index.js b/spec/fixtures/packages/package-with-directory-provider/index.js new file mode 100644 index 000000000..8369d97bb --- /dev/null +++ b/spec/fixtures/packages/package-with-directory-provider/index.js @@ -0,0 +1,47 @@ +'use strict' + +class FakeRemoteDirectory { + constructor (uri) { + this.uri = uri + } + + relativize (uri) { + return uri + } + + getPath () { + return this.uri + } + + isRoot () { + return true + } + + getSubdirectory () { + return { + existsSync () { + return false + } + } + } + + existsSync () { + return true + } + + contains () { + return false + } +} + +exports.provideDirectoryProvider = function () { + return { + name: 'directory provider from package-with-directory-provider', + + directoryForURISync (uri) { + if (uri.startsWith('remote://')) { + return new FakeRemoteDirectory(uri) + } + } + } +} diff --git a/spec/fixtures/packages/package-with-directory-provider/package.json b/spec/fixtures/packages/package-with-directory-provider/package.json new file mode 100644 index 000000000..15c6150f8 --- /dev/null +++ b/spec/fixtures/packages/package-with-directory-provider/package.json @@ -0,0 +1,12 @@ +{ + "name": "package-with-directory-provider", + + "providedServices": { + "atom.directory-provider": { + "description": "Provides custom Directory instances", + "versions": { + "0.1.1": "provideDirectoryProvider" + } + } + } +} diff --git a/spec/git-repository-spec.coffee b/spec/git-repository-spec.coffee index d427fc3b9..c9a3badb5 100644 --- a/spec/git-repository-spec.coffee +++ b/spec/git-repository-spec.coffee @@ -2,7 +2,6 @@ temp = require 'temp' GitRepository = require '../src/git-repository' fs = require 'fs-plus' path = require 'path' -Task = require '../src/task' Project = require '../src/project' copyRepository = -> @@ -195,7 +194,7 @@ describe "GitRepository", -> expect(repo.isStatusModified(repo.getDirectoryStatus(directoryPath))).toBe true describe ".refreshStatus()", -> - [newPath, modifiedPath, cleanPath, originalModifiedPathText, workingDirectory] = [] + [newPath, modifiedPath, cleanPath, workingDirectory] = [] beforeEach -> workingDirectory = copyRepository() diff --git a/spec/jasmine-test-runner.coffee b/spec/jasmine-test-runner.coffee index dd8386f5d..dcfe4448e 100644 --- a/spec/jasmine-test-runner.coffee +++ b/spec/jasmine-test-runner.coffee @@ -1,5 +1,4 @@ Grim = require 'grim' -_ = require 'underscore-plus' fs = require 'fs-plus' path = require 'path' {ipcRenderer} = require 'electron' diff --git a/spec/lines-yardstick-spec.coffee b/spec/lines-yardstick-spec.coffee index bb0294b54..483a77675 100644 --- a/spec/lines-yardstick-spec.coffee +++ b/spec/lines-yardstick-spec.coffee @@ -1,6 +1,5 @@ LinesYardstick = require '../src/lines-yardstick' LineTopIndex = require 'line-top-index' -{toArray} = require 'underscore-plus' {Point} = require 'text-buffer' describe "LinesYardstick", -> @@ -15,8 +14,6 @@ describe "LinesYardstick", -> runs -> createdLineNodes = [] - availableScreenRows = {} - screenRowsToMeasure = [] buildLineNode = (screenRow) -> startIndex = 0 @@ -43,11 +40,13 @@ describe "LinesYardstick", -> mockLineNodesProvider = lineNodesById: {} + lineIdForScreenRow: (screenRow) -> - editor.screenLineForScreenRow(screenRow).id + editor.screenLineForScreenRow(screenRow)?.id lineNodeForScreenRow: (screenRow) -> - @lineNodesById[@lineIdForScreenRow(screenRow)] ?= buildLineNode(screenRow) + if id = @lineIdForScreenRow(screenRow) + @lineNodesById[id] ?= buildLineNode(screenRow) textNodesForScreenRow: (screenRow) -> lineNode = @lineNodeForScreenRow(screenRow) diff --git a/spec/main-process/atom-application.test.js b/spec/main-process/atom-application.test.js index b967e6ce2..22902d3d8 100644 --- a/spec/main-process/atom-application.test.js +++ b/spec/main-process/atom-application.test.js @@ -188,6 +188,8 @@ describe('AtomApplication', function () { reusedWindow = atomApplication.launch(parseCommandLine([dirBPath, '-a'])) assert.equal(reusedWindow, window1) assert.deepEqual(atomApplication.windows, [window1]) + + await conditionPromise(async () => (await getTreeViewRootDirectories(reusedWindow)).length === 3) assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath, dirCPath, dirBPath]) }) @@ -246,7 +248,7 @@ describe('AtomApplication', function () { const reusedWindow = atomApplication.launch(parseCommandLine([tempDirPath])) assert.equal(reusedWindow, window1) - assert.deepEqual(await getTreeViewRootDirectories(window1), [tempDirPath]) + await conditionPromise(async () => (await getTreeViewRootDirectories(reusedWindow)).length > 0) }) it('opens a new window with a single untitled buffer when launched with no path, even if windows already exist', async function () { @@ -303,33 +305,33 @@ describe('AtomApplication', function () { assert.deepEqual(await getTreeViewRootDirectories(window), [path.dirname(newFilePath)]) }) - it('opens an empty text editor and loads its parent directory in the tree-view when launched with a new file path in a remote directory', async function () { - // Disable the tree-view because it will try to enumerate the contents of - // the remote directory and, since it doesn't exist, throw an error. - const configPath = path.join(process.env.ATOM_HOME, 'config.cson') - const config = season.readFileSync(configPath) - if (!config['*'].core) config['*'].core = {} - config['*'].core.disabledPackages = ['tree-view'] - season.writeFileSync(configPath, config) + it('adds a remote directory to the project when launched with a remote directory', async function () { + const packagePath = path.join(__dirname, '..', 'fixtures', 'packages', 'package-with-directory-provider') + const packagesDirPath = path.join(process.env.ATOM_HOME, 'packages') + fs.mkdirSync(packagesDirPath) + fs.symlinkSync(packagePath, path.join(packagesDirPath, 'package-with-directory-provider')) const atomApplication = buildAtomApplication() - const newRemoteFilePath = 'remote://server:3437/some/directory/path' - const window = atomApplication.launch(parseCommandLine([newRemoteFilePath])) + atomApplication.config.set('core.disabledPackages', ['fuzzy-finder']) + + const remotePath = 'remote://server:3437/some/directory/path' + let window = atomApplication.launch(parseCommandLine([remotePath])) + await focusWindow(window) - const {projectPaths, editorTitle, editorText} = await evalInWebContents(window.browserWindow.webContents, function (sendBackToMainProcess) { - atom.workspace.observeActivePaneItem(function (editor) { - if (editor) { - sendBackToMainProcess({ - projectPaths: atom.project.getPaths(), - editorTitle: editor.getTitle(), - editorText: editor.getText() - }) - } + await conditionPromise(async () => (await getProjectDirectories()).length > 0) + let directories = await getProjectDirectories() + assert.deepEqual(directories, [{type: 'FakeRemoteDirectory', path: remotePath}]) + + await window.reload() + await focusWindow(window) + directories = await getProjectDirectories() + assert.deepEqual(directories, [{type: 'FakeRemoteDirectory', path: remotePath}]) + + function getProjectDirectories () { + return evalInWebContents(window.browserWindow.webContents, function (sendBackToMainProcess) { + sendBackToMainProcess(atom.project.getDirectories().map(d => ({ type: d.constructor.name, path: d.getPath() }))) }) - }) - assert.deepEqual(projectPaths, [newRemoteFilePath]) - assert.equal(editorTitle, path.basename(newRemoteFilePath)) - assert.equal(editorText, '') + } }) it('reopens any previously opened windows when launched with no path', async function () { @@ -342,6 +344,9 @@ describe('AtomApplication', function () { const app1Window2 = atomApplication1.launch(parseCommandLine([tempDirPath2])) await app1Window2.loadedPromise + await app1Window1.saveState() + await app1Window2.saveState() + const atomApplication2 = buildAtomApplication() const [app2Window1, app2Window2] = atomApplication2.launch(parseCommandLine([])) await app2Window1.loadedPromise diff --git a/spec/package-manager-spec.coffee b/spec/package-manager-spec.coffee index 43e21eeff..62e96f81c 100644 --- a/spec/package-manager-spec.coffee +++ b/spec/package-manager-spec.coffee @@ -31,6 +31,19 @@ describe "PackageManager", -> it "returns the value of the core.apmPath config setting", -> expect(atom.packages.getApmPath()).toBe "/path/to/apm" + describe "::loadPackages()", -> + beforeEach -> + spyOn(atom.packages, 'loadPackage') + + afterEach -> + atom.packages.deactivatePackages() + atom.packages.unloadPackages() + + it "sets hasLoadedInitialPackages", -> + expect(atom.packages.hasLoadedInitialPackages()).toBe false + atom.packages.loadPackages() + expect(atom.packages.hasLoadedInitialPackages()).toBe true + describe "::loadPackage(name)", -> beforeEach -> atom.config.set("core.disabledPackages", []) @@ -120,6 +133,16 @@ describe "PackageManager", -> state: state2 } + it "early-activates any atom.directory-provider or atom.repository-provider services that the package provide", -> + jasmine.useRealClock() + + providers = [] + atom.packages.serviceHub.consume 'atom.directory-provider', '^0.1.0', (provider) -> + providers.push(provider) + + atom.packages.loadPackage('package-with-directory-provider') + expect(providers.map((p) -> p.name)).toEqual(['directory provider from package-with-directory-provider']) + describe "when there are view providers specified in the package's package.json", -> model1 = {worksWithViewProvider1: true} model2 = {worksWithViewProvider2: true} @@ -492,6 +515,7 @@ describe "PackageManager", -> runs -> expect(pack.mainModule.someNumber).not.toBe 77 pack.mainModule.someNumber = 77 + atom.packages.serializePackage("package-with-serialization") atom.packages.deactivatePackage("package-with-serialization") spyOn(pack.mainModule, 'activate').andCallThrough() waitsForPromise -> @@ -889,6 +913,22 @@ describe "PackageManager", -> expect(atom.packages.packageStates['package-with-serialization']).toEqual someNumber: 1 expect(console.error).toHaveBeenCalled() + describe "::deactivatePackages()", -> + it "deactivates all packages but does not serialize them", -> + [pack1, pack2] = [] + + waitsForPromise -> + atom.packages.activatePackage("package-with-deactivate").then (p) -> pack1 = p + atom.packages.activatePackage("package-with-serialization").then (p) -> pack2 = p + + runs -> + spyOn(pack1.mainModule, 'deactivate') + spyOn(pack2.mainModule, 'serialize') + atom.packages.deactivatePackages() + + expect(pack1.mainModule.deactivate).toHaveBeenCalled() + expect(pack2.mainModule.serialize).not.toHaveBeenCalled() + describe "::deactivatePackage(id)", -> afterEach -> atom.packages.unloadPackages() @@ -995,6 +1035,12 @@ describe "PackageManager", -> jasmine.restoreDeprecationsSnapshot() + it "sets hasActivatedInitialPackages", -> + spyOn(atom.packages, 'activatePackages') + expect(atom.packages.hasActivatedInitialPackages()).toBe false + waitsForPromise -> atom.packages.activate() + runs -> expect(atom.packages.hasActivatedInitialPackages()).toBe true + it "activates all the packages, and none of the themes", -> packageActivator = spyOn(atom.packages, 'activatePackages') themeActivator = spyOn(atom.themes, 'activatePackages') diff --git a/spec/package-spec.coffee b/spec/package-spec.coffee index 57d7a661f..a0e7ffa4d 100644 --- a/spec/package-spec.coffee +++ b/spec/package-spec.coffee @@ -197,7 +197,7 @@ describe "Package", -> expect(spy).toHaveBeenCalled() describe ".loadMetadata()", -> - [packagePath, pack, metadata] = [] + [packagePath, metadata] = [] beforeEach -> packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-different-directory-name') diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee index ac4d502b9..d8f74db53 100644 --- a/spec/pane-spec.coffee +++ b/spec/pane-spec.coffee @@ -2,7 +2,6 @@ {Emitter} = require 'event-kit' Grim = require 'grim' Pane = require '../src/pane' -PaneAxis = require '../src/pane-axis' PaneContainer = require '../src/pane-container' describe "Pane", -> diff --git a/spec/panel-container-element-spec.coffee b/spec/panel-container-element-spec.coffee index 65577221a..55e6f7133 100644 --- a/spec/panel-container-element-spec.coffee +++ b/spec/panel-container-element-spec.coffee @@ -1,7 +1,5 @@ Panel = require '../src/panel' -PanelElement = require '../src/panel-element' PanelContainer = require '../src/panel-container' -PanelContainerElement = require '../src/panel-container-element' describe "PanelContainerElement", -> [jasmineContent, element, container] = [] diff --git a/spec/project-spec.coffee b/spec/project-spec.coffee index 765463f2f..30415a059 100644 --- a/spec/project-spec.coffee +++ b/spec/project-spec.coffee @@ -1,10 +1,7 @@ temp = require 'temp' -fstream = require 'fstream' Project = require '../src/project' -_ = require 'underscore-plus' fs = require 'fs-plus' path = require 'path' -BufferedProcess = require '../src/buffered-process' {Directory} = require 'pathwatcher' GitRepository = require '../src/git-repository' @@ -25,7 +22,6 @@ describe "Project", -> deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm}) state = atom.project.serialize() state.paths.push('/directory/that/does/not/exist') - state.paths.push(path.join(__dirname, 'fixtures', 'sample.js')) deserializedProject.deserialize(state, atom.deserializers) expect(deserializedProject.getPaths()).toEqual(atom.project.getPaths()) @@ -231,31 +227,22 @@ describe "Project", -> expect(directories[1].getPath()).toBe remotePath expect(directories[1] instanceof DummyDirectory).toBe true - # It does not add new remote paths if their directories do not exist - # and they are contained by existing remote paths. - childRemotePath = remotePath + "/subdirectory/that/does-not-exist" - atom.project.addPath(childRemotePath) + # It does not add new remote paths that do not exist + nonExistentRemotePath = "ssh://another-directory:8080/does-not-exist" + atom.project.addPath(nonExistentRemotePath) expect(atom.project.getDirectories().length).toBe 2 - # It does add new remote paths if their directories exist. - childRemotePath = remotePath + "/subdirectory/that/does-exist" - atom.project.addPath(childRemotePath) + # It adds new remote paths if their directories exist. + newRemotePath = "ssh://another-directory:8080/does-exist" + atom.project.addPath(newRemotePath) directories = atom.project.getDirectories() - expect(directories[2].getPath()).toBe childRemotePath + expect(directories[2].getPath()).toBe newRemotePath expect(directories[2] instanceof DummyDirectory).toBe true - # It does add new remote paths to be added if they are not contained by - # previous remote paths. - otherRemotePath = "ssh://other-foreign-directory:8080/" - atom.project.addPath(otherRemotePath) - directories = atom.project.getDirectories() - expect(directories[3].getPath()).toBe otherRemotePath - expect(directories[3] instanceof DummyDirectory).toBe true - it "stops using the provider when the service is removed", -> serviceDisposable.dispose() atom.project.setPaths(["ssh://foreign-directory:8080/does-exist"]) - expect(atom.project.getDirectories()[0] instanceof Directory).toBe true + expect(atom.project.getDirectories().length).toBe(0) describe ".open(path)", -> [absolutePath, newBufferHandler] = [] @@ -429,13 +416,6 @@ describe "Project", -> expect(atom.project.getPaths()[0]).toEqual path.dirname(require.resolve('./fixtures/dir/a')) expect(atom.project.getDirectories()[0].path).toEqual path.dirname(require.resolve('./fixtures/dir/a')) - it "only normalizes the directory path if it isn't on the local filesystem", -> - nonLocalFsDirectory = "custom_proto://abc/def" - atom.project.setPaths([nonLocalFsDirectory]) - directories = atom.project.getDirectories() - expect(directories.length).toBe 1 - expect(directories[0].getPath()).toBe path.normalize(nonLocalFsDirectory) - describe ".addPath(path)", -> it "calls callbacks registered with ::onDidChangePaths", -> onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths spy') @@ -470,6 +450,11 @@ describe "Project", -> expect(atom.project.getPaths()).toEqual([oldPath, newPath]) expect(onDidChangePathsSpy).toHaveBeenCalled() + it "doesn't add non-existent directories", -> + previousPaths = atom.project.getPaths() + atom.project.addPath('/this-definitely/does-not-exist') + expect(atom.project.getPaths()).toEqual(previousPaths) + describe ".removePath(path)", -> onDidChangePathsSpy = null diff --git a/spec/selection-spec.coffee b/spec/selection-spec.coffee index 7511c4b39..1b21d7411 100644 --- a/spec/selection-spec.coffee +++ b/spec/selection-spec.coffee @@ -5,7 +5,7 @@ describe "Selection", -> beforeEach -> buffer = atom.project.bufferForPathSync('sample.js') - editor = atom.workspace.buildTextEditor(buffer: buffer, tabLength: 2) + editor = new TextEditor({buffer: buffer, tabLength: 2}) selection = editor.getLastSelection() afterEach -> diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 55d3e8dbf..7dbd6a4e5 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -49,7 +49,7 @@ if process.env.CI else jasmine.getEnv().defaultTimeoutInterval = 5000 -{resourcePath, testPaths} = atom.getLoadSettings() +{testPaths} = atom.getLoadSettings() if specPackagePath = FindParentDir.sync(testPaths[0], 'package.json') packageMetadata = require(path.join(specPackagePath, 'package.json')) @@ -61,8 +61,6 @@ else specProjectPath = path.join(__dirname, 'fixtures') beforeEach -> - documentTitle = null - atom.project.setPaths([specProjectPath]) window.resetTimeouts() diff --git a/spec/squirrel-update-spec.coffee b/spec/squirrel-update-spec.coffee index 2fe944e41..083b1f78d 100644 --- a/spec/squirrel-update-spec.coffee +++ b/spec/squirrel-update-spec.coffee @@ -4,7 +4,6 @@ path = require 'path' temp = require 'temp' SquirrelUpdate = require '../src/main-process/squirrel-update' Spawner = require '../src/main-process/spawner' -WinPowerShell = require '../src/main-process/win-powershell' WinShell = require '../src/main-process/win-shell' # Run passed callback as Spawner.spawn() would do diff --git a/spec/task-spec.coffee b/spec/task-spec.coffee index 934589f2b..b2a974d60 100644 --- a/spec/task-spec.coffee +++ b/spec/task-spec.coffee @@ -8,7 +8,6 @@ describe "Task", -> task = Task.once require.resolve('./fixtures/task-spec-handler'), (result) -> handlerResult = result - processClosed = false processErrored = false childProcess = task.childProcess spyOn(childProcess, 'kill').andCallThrough() diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index afbb3a50e..7de1c6ccd 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -2,6 +2,7 @@ import {it, fit, ffit, fffit, beforeEach, afterEach, conditionPromise} from './async-spec-helpers' import Grim from 'grim' +import TextEditor from '../src/text-editor' import TextEditorElement from '../src/text-editor-element' import _, {extend, flatten, last, toArray} from 'underscore-plus' @@ -4419,7 +4420,7 @@ describe('TextEditorComponent', function () { describe('when autoHeight is not assigned on the editor', function () { it('implicitly assigns autoHeight to true and emits a deprecation warning if the editor has its height assigned via an inline style', function () { - editor = atom.workspace.buildTextEditor() + editor = new TextEditor() element = editor.getElement() element.setUpdatedSynchronously(false) element.style.height = '200px' @@ -4434,7 +4435,7 @@ describe('TextEditorComponent', function () { }) it('implicitly assigns autoHeight to true and emits a deprecation warning if the editor has its height assigned via position absolute with an assigned top and bottom', function () { - editor = atom.workspace.buildTextEditor() + editor = new TextEditor() element = editor.getElement() element.setUpdatedSynchronously(false) parentElement = document.createElement('div') diff --git a/spec/text-editor-element-spec.coffee b/spec/text-editor-element-spec.coffee index 803285dd3..22b921647 100644 --- a/spec/text-editor-element-spec.coffee +++ b/spec/text-editor-element-spec.coffee @@ -1,5 +1,5 @@ -TextEditorElement = require '../src/text-editor-element' TextEditor = require '../src/text-editor' +TextEditorElement = require '../src/text-editor-element' {Disposable} = require 'event-kit' # The rest of text-editor-component-spec will be moved to this file when React @@ -34,7 +34,7 @@ describe "TextEditorElement", -> describe "when the model is assigned", -> it "adds the 'mini' attribute if .isMini() returns true on the model", -> element = new TextEditorElement - model = atom.workspace.buildTextEditor(mini: true) + model = new TextEditor({mini: true}) element.setModel(model) expect(element.hasAttribute('mini')).toBe true @@ -53,7 +53,7 @@ describe "TextEditorElement", -> describe "when the editor is detached from the DOM and then reattached", -> it "does not render duplicate line numbers", -> - editor = atom.workspace.buildTextEditor() + editor = new TextEditor editor.setText('1\n2\n3') element = atom.views.getView(editor) @@ -66,7 +66,7 @@ describe "TextEditorElement", -> expect(element.shadowRoot.querySelectorAll('.line-number').length).toBe initialCount it "does not render duplicate decorations in custom gutters", -> - editor = atom.workspace.buildTextEditor() + editor = new TextEditor editor.setText('1\n2\n3') editor.addGutter({name: 'test-gutter'}) marker = editor.markBufferRange([[0, 0], [2, 0]]) @@ -201,7 +201,7 @@ describe "TextEditorElement", -> describe "::getMaxScrollTop", -> it "returns the maximum scroll top that can be applied to the element", -> - editor = atom.workspace.buildTextEditor() + editor = new TextEditor editor.setText('1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16') element = atom.views.getView(editor) element.style.lineHeight = "10px" diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 728768799..f4760ece1 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -19,7 +19,7 @@ describe "TextEditorPresenter", -> spyOn(window, "clearInterval").andCallFake window.fakeClearInterval buffer = new TextBuffer(filePath: require.resolve('./fixtures/sample.js')) - editor = atom.workspace.buildTextEditor({buffer}) + editor = new TextEditor({buffer}) waitsForPromise -> buffer.load() afterEach -> @@ -475,6 +475,7 @@ describe "TextEditorPresenter", -> waitsForPromise -> atom.packages.activatePackage('language-javascript') runs -> + editor.setGrammar(atom.grammars.grammarForScopeName('source.js')) maxLineLength = editor.getMaxScreenLineLength() presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10) @@ -759,6 +760,7 @@ describe "TextEditorPresenter", -> waitsForPromise -> atom.packages.activatePackage('language-javascript') runs -> + editor.setGrammar(atom.grammars.grammarForScopeName('source.js')) editor.setCursorBufferPosition([3, 6]) presenter = buildPresenter() expect(getState(presenter).hiddenInput.width).toBe 10 @@ -918,6 +920,7 @@ describe "TextEditorPresenter", -> waitsForPromise -> atom.packages.activatePackage('language-javascript') runs -> + editor.setGrammar(atom.grammars.grammarForScopeName('source.js')) maxLineLength = editor.getMaxScreenLineLength() presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10) @@ -1265,6 +1268,8 @@ describe "TextEditorPresenter", -> expectValues lineStateForScreenRow(presenter, 3), {screenRow: 3, tagCodes: editor.screenLineForScreenRow(3).tagCodes} it "includes the .endOfLineInvisibles if the editor.showInvisibles config option is true", -> + editor.update({showInvisibles: false, invisibles: {eol: 'X'}}) + editor.setText("hello\nworld\r\n") presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10) expect(tagsForCodes(presenter, lineStateForScreenRow(presenter, 0).tagCodes).openTags).not.toContain('invisible-character eol') @@ -1731,6 +1736,7 @@ describe "TextEditorPresenter", -> atom.packages.activatePackage('language-javascript') runs -> + editor.setGrammar(atom.grammars.grammarForScopeName('source.js')) editor.setCursorBufferPosition([1, 4]) presenter = buildPresenter(explicitHeight: 20) @@ -1848,7 +1854,7 @@ describe "TextEditorPresenter", -> describe ".highlights", -> expectUndefinedStateForHighlight = (presenter, decoration) -> - for tileId, tileState of getState(presenter).content.tiles + for tileId of getState(presenter).content.tiles state = stateForHighlightInTile(presenter, decoration, tileId) expect(state).toBeUndefined() @@ -1860,7 +1866,7 @@ describe "TextEditorPresenter", -> stateForHighlightInTile(presenter, selection.decoration, tile) expectUndefinedStateForSelection = (presenter, selectionIndex) -> - for tileId, tileState of getState(presenter).content.tiles + for tileId of getState(presenter).content.tiles state = stateForSelectionInTile(presenter, selectionIndex, tileId) expect(state).toBeUndefined() @@ -2076,6 +2082,7 @@ describe "TextEditorPresenter", -> atom.packages.activatePackage('language-javascript') runs -> + editor.setGrammar(atom.grammars.grammarForScopeName('source.js')) editor.setSelectedBufferRanges([ [[2, 4], [2, 6]], ]) @@ -3667,7 +3674,7 @@ describe "TextEditorPresenter", -> performSetup = -> buffer = new TextBuffer - editor = atom.workspace.buildTextEditor({buffer}) + editor = new TextEditor({buffer}) editor.setEditorWidthInChars(80) presenterParams = model: editor diff --git a/spec/text-editor-registry-spec.js b/spec/text-editor-registry-spec.js index 812676305..51027e63c 100644 --- a/spec/text-editor-registry-spec.js +++ b/spec/text-editor-registry-spec.js @@ -15,15 +15,11 @@ describe('TextEditorRegistry', function () { registry = new TextEditorRegistry({ assert: atom.assert, config: atom.config, - clipboard: atom.clipboard, grammarRegistry: atom.grammars, packageManager: {deferredActivationHooks: null} }) - editor = new TextEditor({ - config: atom.config, - clipboard: atom.clipboard, - }) + editor = new TextEditor() }) afterEach(function () { @@ -125,6 +121,25 @@ describe('TextEditorRegistry', function () { expect(editor.getGrammar().name).toBe('Null Grammar') expect(retainedEditorCount(registry)).toBe(0) }) + + describe('when called twice with a given editor', function () { + it('does nothing the second time', async function () { + await atom.packages.activatePackage('language-javascript') + const disposable1 = registry.maintainGrammar(editor) + const disposable2 = registry.maintainGrammar(editor) + + editor.getBuffer().setPath('test.js') + expect(editor.getGrammar().name).toBe('JavaScript') + + disposable2.dispose() + editor.getBuffer().setPath('test.txt') + expect(editor.getGrammar().name).toBe('Null Grammar') + + disposable1.dispose() + editor.getBuffer().setPath('test.js') + expect(editor.getGrammar().name).toBe('Null Grammar') + }) + }) }) describe('.setGrammarOverride', function () { @@ -175,10 +190,7 @@ describe('TextEditorRegistry', function () { it('does not update the editor when config settings change for unrelated scope selectors', async function () { await atom.packages.activatePackage('language-javascript') - const editor2 = new TextEditor({ - config: atom.config, - clipboard: atom.clipboard, - }) + const editor2 = new TextEditor() editor2.setGrammar(atom.grammars.selectGrammar('test.js')) @@ -186,13 +198,13 @@ describe('TextEditorRegistry', function () { registry.maintainConfig(editor2) await initialPackageActivation - expect(editor.getRootScopeDescriptor().getScopesArray()).toEqual(['text.plain']) + expect(editor.getRootScopeDescriptor().getScopesArray()).toEqual(['text.plain.null-grammar']) expect(editor2.getRootScopeDescriptor().getScopesArray()).toEqual(['source.js']) expect(editor.getEncoding()).toBe('utf8') expect(editor2.getEncoding()).toBe('utf8') - atom.config.set('core.fileEncoding', 'utf16le', {scopeSelector: '.text.plain'}) + atom.config.set('core.fileEncoding', 'utf16le', {scopeSelector: '.text.plain.null-grammar'}) atom.config.set('core.fileEncoding', 'utf16be', {scopeSelector: '.source.js'}) expect(editor.getEncoding()).toBe('utf16le') @@ -205,7 +217,6 @@ describe('TextEditorRegistry', function () { registry = new TextEditorRegistry({ assert: atom.assert, config: atom.config, - clipboard: atom.clipboard, grammarRegistry: atom.grammars, packageManager: { deferredActivationHooks: [], @@ -625,14 +636,32 @@ describe('TextEditorRegistry', function () { expect(delegate.getNonWordCharacters(['e.f', 'g.h'])).toBe('(){}[]') expect(delegate.getNonWordCharacters(['i.j'])).toBe('()') }) + + describe('when called twice with a given editor', function () { + it('does nothing the second time', async function () { + editor.update({scrollSensitivity: 50}) + + const disposable1 = registry.maintainConfig(editor) + const disposable2 = registry.maintainConfig(editor) + await initialPackageActivation + + atom.config.set('editor.scrollSensitivity', 60) + expect(editor.getScrollSensitivity()).toBe(60) + + disposable2.dispose() + atom.config.set('editor.scrollSensitivity', 70) + expect(editor.getScrollSensitivity()).toBe(70) + + disposable1.dispose() + atom.config.set('editor.scrollSensitivity', 80) + expect(editor.getScrollSensitivity()).toBe(70) + }) + }) }) describe('serialization', function () { it('persists editors\' grammar overrides', async function () { - const editor2 = new TextEditor({ - config: atom.config, - clipboard: atom.clipboard, - }) + const editor2 = new TextEditor() await atom.packages.activatePackage('language-c') await atom.packages.activatePackage('language-html') @@ -651,7 +680,6 @@ describe('TextEditorRegistry', function () { const registryCopy = new TextEditorRegistry({ assert: atom.assert, config: atom.config, - clipboard: atom.clipboard, grammarRegistry: atom.grammars, packageManager: {deferredActivationHooks: null} }) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 1737abe7c..c1c7141c3 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -1,6 +1,4 @@ -fs = require 'fs-plus' path = require 'path' -temp = require 'temp' clipboard = require '../src/safe-clipboard' TextEditor = require '../src/text-editor' TextBuffer = require 'text-buffer' @@ -56,7 +54,6 @@ describe "TextEditor", -> # reusing the same buffer instance editor2 = TextEditor.deserialize(editor.serialize(), { assert: atom.assert, - clipboard: atom.clipboard, textEditors: atom.textEditors, project: { bufferForIdSync: (id) -> TextBuffer.deserialize(editor.buffer.serialize()) @@ -4751,7 +4748,7 @@ describe "TextEditor", -> it "deletes the entire file from the bottom up", -> count = buffer.getLineCount() expect(count).toBeGreaterThan(0) - for line in [0...count] + for [0...count] editor.getLastCursor().moveToBottom() editor.deleteLine() expect(buffer.getLineCount()).toBe(1) @@ -4760,7 +4757,7 @@ describe "TextEditor", -> it "deletes the entire file from the top down", -> count = buffer.getLineCount() expect(count).toBeGreaterThan(0) - for line in [0...count] + for [0...count] editor.getLastCursor().moveToTop() editor.deleteLine() expect(buffer.getLineCount()).toBe(1) @@ -4878,6 +4875,11 @@ describe "TextEditor", -> editor.setTabLength(6) expect(changeHandler).not.toHaveBeenCalled() + it 'does not change its tab length when the given tab length is null', -> + editor.setTabLength(4) + editor.setTabLength(null) + expect(editor.getTabLength()).toBe(4) + describe ".indentLevelForLine(line)", -> it "returns the indent level when the line has only leading whitespace", -> expect(editor.indentLevelForLine(" hello")).toBe(2) @@ -5522,7 +5524,7 @@ describe "TextEditor", -> describe "auto height", -> it "returns true by default but can be customized", -> - editor = atom.workspace.buildTextEditor() + editor = new TextEditor expect(editor.getAutoHeight()).toBe(true) editor.update({autoHeight: false}) expect(editor.getAutoHeight()).toBe(false) @@ -5540,10 +5542,10 @@ describe "TextEditor", -> describe '.get/setPlaceholderText()', -> it 'can be created with placeholderText', -> - newEditor = atom.workspace.buildTextEditor( + newEditor = new TextEditor({ mini: true placeholderText: 'yep' - ) + }) expect(newEditor.getPlaceholderText()).toBe 'yep' it 'models placeholderText and emits an event when changed', -> @@ -5828,11 +5830,7 @@ describe "TextEditor", -> atom.packages.activatePackage('language-coffee-script') it "sets the grammar", -> - editor = new TextEditor({ - grammar: atom.grammars.grammarForScopeName('source.coffee') - clipboard: atom.clipboard - }) - + editor = new TextEditor({grammar: atom.grammars.grammarForScopeName('source.coffee')}) expect(editor.getGrammar().name).toBe 'CoffeeScript' describe "softWrapAtPreferredLineLength", -> diff --git a/spec/theme-manager-spec.coffee b/spec/theme-manager-spec.coffee index 47b848809..68693dddc 100644 --- a/spec/theme-manager-spec.coffee +++ b/spec/theme-manager-spec.coffee @@ -2,12 +2,7 @@ path = require 'path' fs = require 'fs-plus' temp = require 'temp' -Package = require '../src/package' - describe "atom.themes", -> - resourcePath = atom.getLoadSettings().resourcePath - configDirPath = atom.getConfigDirPath() - beforeEach -> spyOn(console, 'warn') diff --git a/spec/time-reporter.coffee b/spec/time-reporter.coffee index e1e9c6a0e..16943c18b 100644 --- a/spec/time-reporter.coffee +++ b/spec/time-reporter.coffee @@ -60,7 +60,7 @@ class TimeReporter extends jasmine.Reporter time: duration fullName: spec.getFullName() - if timedSuites[@suite] + if window.timedSuites[@suite] window.timedSuites[@suite] += duration else window.timedSuites[@suite] = duration diff --git a/spec/token-iterator-spec.coffee b/spec/token-iterator-spec.coffee index f876d30d1..6ae01cd30 100644 --- a/spec/token-iterator-spec.coffee +++ b/spec/token-iterator-spec.coffee @@ -29,7 +29,7 @@ describe "TokenIterator", -> }) tokenizedBuffer.setGrammar(grammar) - tokenIterator = tokenizedBuffer.tokenizedLineForRow(1).getTokenIterator() + tokenIterator = tokenizedBuffer.tokenizedLines[1].getTokenIterator() tokenIterator.next() expect(tokenIterator.getBufferStart()).toBe 0 diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index 9a2845d16..6558d42b4 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -1,3 +1,4 @@ +NullGrammar = require '../src/null-grammar' TokenizedBuffer = require '../src/tokenized-buffer' {Point} = TextBuffer = require 'text-buffer' _ = require 'underscore-plus' @@ -32,15 +33,8 @@ describe "TokenizedBuffer", -> atom.packages.activatePackage('language-coffee-script') it "deserializes it searching among the buffers in the current project", -> - tokenizedBufferA = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBufferB = TokenizedBuffer.deserialize( - JSON.parse(JSON.stringify(tokenizedBufferA.serialize())), - atom - ) - + tokenizedBufferA = new TokenizedBuffer({buffer, tabLength: 2}) + tokenizedBufferB = TokenizedBuffer.deserialize(JSON.parse(JSON.stringify(tokenizedBufferA.serialize())), atom) expect(tokenizedBufferB.buffer).toBe(tokenizedBufferA.buffer) describe "when the underlying buffer has no path", -> @@ -48,25 +42,14 @@ describe "TokenizedBuffer", -> buffer = atom.project.bufferForPathSync(null) it "deserializes it searching among the buffers in the current project", -> - tokenizedBufferA = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBufferB = TokenizedBuffer.deserialize( - JSON.parse(JSON.stringify(tokenizedBufferA.serialize())), - atom - ) - + tokenizedBufferA = new TokenizedBuffer({buffer, tabLength: 2}) + tokenizedBufferB = TokenizedBuffer.deserialize(JSON.parse(JSON.stringify(tokenizedBufferA.serialize())), atom) expect(tokenizedBufferB.buffer).toBe(tokenizedBufferA.buffer) describe "when the buffer is destroyed", -> beforeEach -> buffer = atom.project.bufferForPathSync('sample.js') - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(atom.grammars.grammarForScopeName('source.js')) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName('source.js'), tabLength: 2}) startTokenizing(tokenizedBuffer) it "stops tokenization", -> @@ -78,11 +61,7 @@ describe "TokenizedBuffer", -> describe "when the buffer contains soft-tabs", -> beforeEach -> buffer = atom.project.bufferForPathSync('sample.js') - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(atom.grammars.grammarForScopeName('source.js')) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName('source.js'), tabLength: 2}) startTokenizing(tokenizedBuffer) afterEach -> @@ -90,32 +69,29 @@ describe "TokenizedBuffer", -> buffer.release() describe "on construction", -> - it "initially creates un-tokenized screen lines, then tokenizes lines chunk at a time in the background", -> - line0 = tokenizedBuffer.tokenizedLineForRow(0) - expect(line0.tokens).toEqual([value: line0.text, scopes: ['source.js']]) + it "tokenizes lines chunk at a time in the background", -> + line0 = tokenizedBuffer.tokenizedLines[0] + expect(line0).toBeUndefined() - line11 = tokenizedBuffer.tokenizedLineForRow(11) - expect(line11.tokens).toEqual([value: " return sort(Array.apply(this, arguments));", scopes: ['source.js']]) - - # background tokenization has not begun - expect(tokenizedBuffer.tokenizedLineForRow(0).ruleStack).toBeUndefined() + line11 = tokenizedBuffer.tokenizedLines[11] + expect(line11).toBeUndefined() # tokenize chunk 1 advanceClock() - expect(tokenizedBuffer.tokenizedLineForRow(0).ruleStack?).toBeTruthy() - expect(tokenizedBuffer.tokenizedLineForRow(4).ruleStack?).toBeTruthy() - expect(tokenizedBuffer.tokenizedLineForRow(5).ruleStack?).toBeFalsy() + expect(tokenizedBuffer.tokenizedLines[0].ruleStack?).toBeTruthy() + expect(tokenizedBuffer.tokenizedLines[4].ruleStack?).toBeTruthy() + expect(tokenizedBuffer.tokenizedLines[5]).toBeUndefined() # tokenize chunk 2 advanceClock() - expect(tokenizedBuffer.tokenizedLineForRow(5).ruleStack?).toBeTruthy() - expect(tokenizedBuffer.tokenizedLineForRow(9).ruleStack?).toBeTruthy() - expect(tokenizedBuffer.tokenizedLineForRow(10).ruleStack?).toBeFalsy() + expect(tokenizedBuffer.tokenizedLines[5].ruleStack?).toBeTruthy() + expect(tokenizedBuffer.tokenizedLines[9].ruleStack?).toBeTruthy() + expect(tokenizedBuffer.tokenizedLines[10]).toBeUndefined() # tokenize last chunk advanceClock() - expect(tokenizedBuffer.tokenizedLineForRow(10).ruleStack?).toBeTruthy() - expect(tokenizedBuffer.tokenizedLineForRow(12).ruleStack?).toBeTruthy() + expect(tokenizedBuffer.tokenizedLines[10].ruleStack?).toBeTruthy() + expect(tokenizedBuffer.tokenizedLines[12].ruleStack?).toBeTruthy() describe "when the buffer is partially tokenized", -> beforeEach -> @@ -152,8 +128,8 @@ describe "TokenizedBuffer", -> it "does not attempt to tokenize the lines in the change, and preserves the existing invalid row", -> expect(tokenizedBuffer.firstInvalidRow()).toBe 5 buffer.setTextInRange([[6, 0], [7, 0]], "\n\n\n") - expect(tokenizedBuffer.tokenizedLineForRow(6).ruleStack?).toBeFalsy() - expect(tokenizedBuffer.tokenizedLineForRow(7).ruleStack?).toBeFalsy() + expect(tokenizedBuffer.tokenizedLines[6]).toBeUndefined() + expect(tokenizedBuffer.tokenizedLines[7]).toBeUndefined() expect(tokenizedBuffer.firstInvalidRow()).toBe 5 describe "when the buffer is fully tokenized", -> @@ -165,101 +141,101 @@ describe "TokenizedBuffer", -> it "updates tokens to reflect the change", -> buffer.setTextInRange([[0, 0], [2, 0]], "foo()\n7\n") - expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1]).toEqual(value: '(', scopes: ['source.js', 'meta.function-call.js', 'meta.arguments.js', 'punctuation.definition.arguments.begin.bracket.round.js']) - expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: '7', scopes: ['source.js', 'constant.numeric.decimal.js']) + expect(tokenizedBuffer.tokenizedLines[0].tokens[1]).toEqual(value: '(', scopes: ['source.js', 'meta.function-call.js', 'meta.arguments.js', 'punctuation.definition.arguments.begin.bracket.round.js']) + expect(tokenizedBuffer.tokenizedLines[1].tokens[0]).toEqual(value: '7', scopes: ['source.js', 'constant.numeric.decimal.js']) # line 2 is unchanged - expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js']) + expect(tokenizedBuffer.tokenizedLines[2].tokens[1]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js']) describe "when the change invalidates the tokenization of subsequent lines", -> it "schedules the invalidated lines to be tokenized in the background", -> buffer.insert([5, 30], '/* */') buffer.insert([2, 0], '/*') - expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js'] + expect(tokenizedBuffer.tokenizedLines[3].tokens[0].scopes).toEqual ['source.js'] advanceClock() - expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] - expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] - expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[3].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[4].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[5].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] it "resumes highlighting with the state of the previous line", -> buffer.insert([0, 0], '/*') buffer.insert([5, 0], '*/') buffer.insert([1, 0], 'var ') - expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[1].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] describe "when lines are both updated and removed", -> it "updates tokens to reflect the change", -> buffer.setTextInRange([[1, 0], [3, 0]], "foo()") # previous line 0 remains - expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[0]).toEqual(value: 'var', scopes: ['source.js', 'storage.type.var.js']) + expect(tokenizedBuffer.tokenizedLines[0].tokens[0]).toEqual(value: 'var', scopes: ['source.js', 'storage.type.var.js']) # previous line 3 should be combined with input to form line 1 - expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: 'foo', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js']) - expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[6]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js']) + expect(tokenizedBuffer.tokenizedLines[1].tokens[0]).toEqual(value: 'foo', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js']) + expect(tokenizedBuffer.tokenizedLines[1].tokens[6]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js']) # lines below deleted regions should be shifted upward - expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1]).toEqual(value: 'while', scopes: ['source.js', 'keyword.control.js']) - expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[1]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js']) - expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[1]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.comparison.js']) + expect(tokenizedBuffer.tokenizedLines[2].tokens[1]).toEqual(value: 'while', scopes: ['source.js', 'keyword.control.js']) + expect(tokenizedBuffer.tokenizedLines[3].tokens[1]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js']) + expect(tokenizedBuffer.tokenizedLines[4].tokens[1]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.comparison.js']) describe "when the change invalidates the tokenization of subsequent lines", -> it "schedules the invalidated lines to be tokenized in the background", -> buffer.insert([5, 30], '/* */') buffer.setTextInRange([[2, 0], [3, 0]], '/*') - expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js'] - expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js'] + expect(tokenizedBuffer.tokenizedLines[2].tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js'] + expect(tokenizedBuffer.tokenizedLines[3].tokens[0].scopes).toEqual ['source.js'] advanceClock() - expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] - expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[3].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[4].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] describe "when lines are both updated and inserted", -> it "updates tokens to reflect the change", -> buffer.setTextInRange([[1, 0], [2, 0]], "foo()\nbar()\nbaz()\nquux()") # previous line 0 remains - expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[0]).toEqual( value: 'var', scopes: ['source.js', 'storage.type.var.js']) + expect(tokenizedBuffer.tokenizedLines[0].tokens[0]).toEqual( value: 'var', scopes: ['source.js', 'storage.type.var.js']) # 3 new lines inserted - expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: 'foo', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js']) - expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0]).toEqual(value: 'bar', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js']) - expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0]).toEqual(value: 'baz', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js']) + expect(tokenizedBuffer.tokenizedLines[1].tokens[0]).toEqual(value: 'foo', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js']) + expect(tokenizedBuffer.tokenizedLines[2].tokens[0]).toEqual(value: 'bar', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js']) + expect(tokenizedBuffer.tokenizedLines[3].tokens[0]).toEqual(value: 'baz', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js']) # previous line 2 is joined with quux() on line 4 - expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0]).toEqual(value: 'quux', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js']) - expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[4]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js']) + expect(tokenizedBuffer.tokenizedLines[4].tokens[0]).toEqual(value: 'quux', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js']) + expect(tokenizedBuffer.tokenizedLines[4].tokens[4]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js']) # previous line 3 is pushed down to become line 5 - expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[3]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js']) + expect(tokenizedBuffer.tokenizedLines[5].tokens[3]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js']) describe "when the change invalidates the tokenization of subsequent lines", -> it "schedules the invalidated lines to be tokenized in the background", -> buffer.insert([5, 30], '/* */') buffer.insert([2, 0], '/*\nabcde\nabcder') - expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js'] - expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] - expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] - expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].scopes).toEqual ['source.js'] + expect(tokenizedBuffer.tokenizedLines[2].tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js'] + expect(tokenizedBuffer.tokenizedLines[3].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[4].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[5].tokens[0].scopes).toEqual ['source.js'] advanceClock() # tokenize invalidated lines in background - expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] - expect(tokenizedBuffer.tokenizedLineForRow(6).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] - expect(tokenizedBuffer.tokenizedLineForRow(7).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] - expect(tokenizedBuffer.tokenizedLineForRow(8).tokens[0].scopes).not.toBe ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[5].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[6].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[7].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[8].tokens[0].scopes).not.toBe ['source.js', 'comment.block.js'] describe "when there is an insertion that is larger than the chunk size", -> it "tokenizes the initial chunk synchronously, then tokenizes the remaining lines in the background", -> commentBlock = _.multiplyString("// a comment\n", tokenizedBuffer.chunkSize + 2) buffer.insert([0, 0], commentBlock) - expect(tokenizedBuffer.tokenizedLineForRow(0).ruleStack?).toBeTruthy() - expect(tokenizedBuffer.tokenizedLineForRow(4).ruleStack?).toBeTruthy() - expect(tokenizedBuffer.tokenizedLineForRow(5).ruleStack?).toBeFalsy() + expect(tokenizedBuffer.tokenizedLines[0].ruleStack?).toBeTruthy() + expect(tokenizedBuffer.tokenizedLines[4].ruleStack?).toBeTruthy() + expect(tokenizedBuffer.tokenizedLines[5]).toBeUndefined() advanceClock() - expect(tokenizedBuffer.tokenizedLineForRow(5).ruleStack?).toBeTruthy() - expect(tokenizedBuffer.tokenizedLineForRow(6).ruleStack?).toBeTruthy() + expect(tokenizedBuffer.tokenizedLines[5].ruleStack?).toBeTruthy() + expect(tokenizedBuffer.tokenizedLines[6].ruleStack?).toBeTruthy() it "does not break out soft tabs across a scope boundary", -> waitsForPromise -> @@ -284,11 +260,7 @@ describe "TokenizedBuffer", -> runs -> buffer = atom.project.bufferForPathSync('sample-with-tabs.coffee') - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(atom.grammars.grammarForScopeName('source.coffee')) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName('source.coffee'), tabLength: 2}) startTokenizing(tokenizedBuffer) afterEach -> @@ -352,7 +324,6 @@ describe "TokenizedBuffer", -> expect(tokenizedHandler.callCount).toBe(1) it "retokenizes the buffer", -> - waitsForPromise -> atom.packages.activatePackage('language-ruby-on-rails') @@ -362,14 +333,10 @@ describe "TokenizedBuffer", -> runs -> buffer = atom.project.bufferForPathSync() buffer.setText "
<%= User.find(2).full_name %>
" - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(atom.grammars.selectGrammar('test.erb')) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.selectGrammar('test.erb'), tabLength: 2}) fullyTokenize(tokenizedBuffer) - {tokens} = tokenizedBuffer.tokenizedLineForRow(0) + {tokens} = tokenizedBuffer.tokenizedLines[0] expect(tokens[0]).toEqual value: "
", scopes: ["text.html.ruby"] waitsForPromise -> @@ -377,7 +344,7 @@ describe "TokenizedBuffer", -> runs -> fullyTokenize(tokenizedBuffer) - {tokens} = tokenizedBuffer.tokenizedLineForRow(0) + {tokens} = tokenizedBuffer.tokenizedLines[0] expect(tokens[0]).toEqual value: '<', scopes: ["text.html.ruby", "meta.tag.block.any.html", "punctuation.definition.tag.begin.html"] describe ".tokenForPosition(position)", -> @@ -387,11 +354,7 @@ describe "TokenizedBuffer", -> it "returns the correct token (regression)", -> buffer = atom.project.bufferForPathSync('sample.js') - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(atom.grammars.grammarForScopeName('source.js')) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName('source.js'), tabLength: 2}) fullyTokenize(tokenizedBuffer) expect(tokenizedBuffer.tokenForPosition([1, 0]).scopes).toEqual ["source.js"] expect(tokenizedBuffer.tokenForPosition([1, 1]).scopes).toEqual ["source.js"] @@ -400,16 +363,12 @@ describe "TokenizedBuffer", -> describe ".bufferRangeForScopeAtPosition(selector, position)", -> beforeEach -> buffer = atom.project.bufferForPathSync('sample.js') - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(atom.grammars.grammarForScopeName('source.js')) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName('source.js'), tabLength: 2}) fullyTokenize(tokenizedBuffer) describe "when the selector does not match the token at the position", -> it "returns a falsy value", -> - expect(tokenizedBuffer.bufferRangeForScopeAtPosition('.bogus', [0, 1])).toBeFalsy() + expect(tokenizedBuffer.bufferRangeForScopeAtPosition('.bogus', [0, 1])).toBeUndefined() describe "when the selector matches a single token at the position", -> it "returns the range covered by the token", -> @@ -423,11 +382,7 @@ describe "TokenizedBuffer", -> describe ".indentLevelForRow(row)", -> beforeEach -> buffer = atom.project.bufferForPathSync('sample.js') - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(atom.grammars.grammarForScopeName('source.js')) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName('source.js'), tabLength: 2}) fullyTokenize(tokenizedBuffer) describe "when the line is non-empty", -> @@ -469,7 +424,7 @@ describe "TokenizedBuffer", -> buffer.insert([12, 0], ' ') expect(tokenizedBuffer.indentLevelForRow(13)).toBe 2 - expect(tokenizedBuffer.tokenizedLineForRow(14)).not.toBeDefined() + expect(tokenizedBuffer.tokenizedLines[14]).not.toBeDefined() it "updates the indentLevel of empty lines surrounding a change that inserts lines", -> buffer.insert([7, 0], '\n\n') @@ -499,18 +454,11 @@ describe "TokenizedBuffer", -> expect(tokenizedBuffer.indentLevelForRow(10)).toBe 2 # } describe "::isFoldableAtRow(row)", -> - changes = null - beforeEach -> - changes = [] buffer = atom.project.bufferForPathSync('sample.js') buffer.insert [10, 0], " // multi-line\n // comment\n // block\n" buffer.insert [0, 0], "// multi-line\n// comment\n// block\n" - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(atom.grammars.grammarForScopeName('source.js')) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName('source.js'), tabLength: 2}) fullyTokenize(tokenizedBuffer) it "includes the first line of multi-line comments", -> @@ -573,40 +521,69 @@ describe "TokenizedBuffer", -> expect(tokenizedBuffer.isFoldableAtRow(7)).toBe false expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false + describe "::tokenizedLineForRow(row)", -> + it "returns the tokenized line for a row, or a placeholder line if it hasn't been tokenized yet", -> + buffer = atom.project.bufferForPathSync('sample.js') + grammar = atom.grammars.grammarForScopeName('source.js') + tokenizedBuffer = new TokenizedBuffer({buffer, grammar, tabLength: 2}) + line0 = buffer.lineForRow(0) + + jsScopeStartId = grammar.startIdForScope(grammar.scopeName) + jsScopeEndId = grammar.endIdForScope(grammar.scopeName) + startTokenizing(tokenizedBuffer) + expect(tokenizedBuffer.tokenizedLines[0]).toBeUndefined() + expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe(line0) + expect(tokenizedBuffer.tokenizedLineForRow(0).tags).toEqual([jsScopeStartId, line0.length, jsScopeEndId]) + advanceClock(1) + expect(tokenizedBuffer.tokenizedLines[0]).not.toBeUndefined() + expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe(line0) + expect(tokenizedBuffer.tokenizedLineForRow(0).tags).not.toEqual([jsScopeStartId, line0.length, jsScopeEndId]) + + nullScopeStartId = NullGrammar.startIdForScope(NullGrammar.scopeName) + nullScopeEndId = NullGrammar.endIdForScope(NullGrammar.scopeName) + tokenizedBuffer.setGrammar(NullGrammar) + startTokenizing(tokenizedBuffer) + expect(tokenizedBuffer.tokenizedLines[0]).toBeUndefined() + expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe(line0) + expect(tokenizedBuffer.tokenizedLineForRow(0).tags).toEqual([nullScopeStartId, line0.length, nullScopeEndId]) + advanceClock(1) + expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe(line0) + expect(tokenizedBuffer.tokenizedLineForRow(0).tags).toEqual([nullScopeStartId, line0.length, nullScopeEndId]) + + it "returns undefined if the requested row is outside the buffer range", -> + buffer = atom.project.bufferForPathSync('sample.js') + grammar = atom.grammars.grammarForScopeName('source.js') + tokenizedBuffer = new TokenizedBuffer({buffer, grammar, tabLength: 2}) + fullyTokenize(tokenizedBuffer) + expect(tokenizedBuffer.tokenizedLineForRow(999)).toBeUndefined() + describe "when the buffer is configured with the null grammar", -> - it "uses the placeholder tokens and does not actually tokenize using the grammar", -> - spyOn(atom.grammars.nullGrammar, 'tokenizeLine').andCallThrough() + it "does not actually tokenize using the grammar", -> + spyOn(NullGrammar, 'tokenizeLine').andCallThrough() buffer = atom.project.bufferForPathSync('sample.will-use-the-null-grammar') buffer.setText('a\nb\nc') - - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) + tokenizedBuffer = new TokenizedBuffer({buffer, tabLength: 2}) tokenizeCallback = jasmine.createSpy('onDidTokenize') tokenizedBuffer.onDidTokenize(tokenizeCallback) + expect(tokenizedBuffer.tokenizedLines[0]).toBeUndefined() + expect(tokenizedBuffer.tokenizedLines[1]).toBeUndefined() + expect(tokenizedBuffer.tokenizedLines[2]).toBeUndefined() + expect(tokenizeCallback.callCount).toBe(0) + expect(NullGrammar.tokenizeLine).not.toHaveBeenCalled() + fullyTokenize(tokenizedBuffer) - - expect(tokenizeCallback.callCount).toBe 1 - expect(atom.grammars.nullGrammar.tokenizeLine.callCount).toBe 0 - - expect(tokenizedBuffer.tokenizedLineForRow(0).tokens.length).toBe 1 - expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[0].value).toBe 'a' - expect(tokenizedBuffer.tokenizedLineForRow(1).tokens.length).toBe 1 - expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0].value).toBe 'b' - expect(tokenizedBuffer.tokenizedLineForRow(2).tokens.length).toBe 1 - expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0].value).toBe 'c' + expect(tokenizedBuffer.tokenizedLines[0]).toBeUndefined() + expect(tokenizedBuffer.tokenizedLines[1]).toBeUndefined() + expect(tokenizedBuffer.tokenizedLines[2]).toBeUndefined() + expect(tokenizeCallback.callCount).toBe(0) + expect(NullGrammar.tokenizeLine).not.toHaveBeenCalled() describe "text decoration layer API", -> describe "iterator", -> it "iterates over the syntactic scope boundaries", -> buffer = new TextBuffer(text: "var foo = 1 /*\nhello*/var bar = 2\n") - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(atom.grammars.selectGrammar(".js")) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName("source.js"), tabLength: 2}) fullyTokenize(tokenizedBuffer) iterator = tokenizedBuffer.buildIterator() @@ -658,11 +635,7 @@ describe "TokenizedBuffer", -> runs -> buffer = new TextBuffer(text: "# hello\n# world") - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(atom.grammars.selectGrammar(".coffee")) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName("source.coffee"), tabLength: 2}) fullyTokenize(tokenizedBuffer) iterator = tokenizedBuffer.buildIterator() @@ -691,11 +664,7 @@ describe "TokenizedBuffer", -> }) buffer = new TextBuffer(text: 'start x\nend x\nx') - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(grammar) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar, tabLength: 2}) fullyTokenize(tokenizedBuffer) iterator = tokenizedBuffer.buildIterator() diff --git a/spec/tooltip-manager-spec.coffee b/spec/tooltip-manager-spec.coffee index d4bfc1bd6..6bebd6e76 100644 --- a/spec/tooltip-manager-spec.coffee +++ b/spec/tooltip-manager-spec.coffee @@ -8,7 +8,7 @@ describe "TooltipManager", -> ctrlY = _.humanizeKeystroke("ctrl-y") beforeEach -> - manager = new TooltipManager(keymapManager: atom.keymaps) + manager = new TooltipManager(keymapManager: atom.keymaps, viewRegistry: atom.views) element = document.createElement('div') element.classList.add('foo') jasmine.attachToDOM(element) @@ -16,23 +16,62 @@ describe "TooltipManager", -> hover = (element, fn) -> element.dispatchEvent(new CustomEvent('mouseenter', bubbles: false)) element.dispatchEvent(new CustomEvent('mouseover', bubbles: true)) - advanceClock(manager.defaults.delay.show) + advanceClock(manager.hoverDefaults.delay.show) fn() element.dispatchEvent(new CustomEvent('mouseleave', bubbles: false)) element.dispatchEvent(new CustomEvent('mouseout', bubbles: true)) - advanceClock(manager.defaults.delay.hide) + advanceClock(manager.hoverDefaults.delay.hide) describe "::add(target, options)", -> - it "creates a tooltip based on the given options when hovering over the target element", -> - manager.add element, title: "Title" - hover element, -> - expect(document.body.querySelector(".tooltip")).toHaveText("Title") + describe "when the trigger is 'hover' (the default)", -> + it "creates a tooltip when hovering over the target element", -> + manager.add element, title: "Title" + hover element, -> + expect(document.body.querySelector(".tooltip")).toHaveText("Title") - it "creates a tooltip immediately if the trigger type is manual", -> - disposable = manager.add element, title: "Title", trigger: "manual" - expect(document.body.querySelector(".tooltip")).toHaveText("Title") - disposable.dispose() - expect(document.body.querySelector(".tooltip")).toBeNull() + describe "when the trigger is 'manual'", -> + it "creates a tooltip immediately and only hides it on dispose", -> + disposable = manager.add element, title: "Title", trigger: "manual" + expect(document.body.querySelector(".tooltip")).toHaveText("Title") + disposable.dispose() + expect(document.body.querySelector(".tooltip")).toBeNull() + + describe "when the trigger is 'click'", -> + it "shows and hides the tooltip when the target element is clicked", -> + disposable = manager.add element, title: "Title", trigger: "click" + expect(document.body.querySelector(".tooltip")).toBeNull() + element.click() + expect(document.body.querySelector(".tooltip")).not.toBeNull() + element.click() + expect(document.body.querySelector(".tooltip")).toBeNull() + + # Hide the tooltip when clicking anywhere but inside the tooltip element + element.click() + expect(document.body.querySelector(".tooltip")).not.toBeNull() + document.body.querySelector(".tooltip").click() + expect(document.body.querySelector(".tooltip")).not.toBeNull() + document.body.querySelector(".tooltip").firstChild.click() + expect(document.body.querySelector(".tooltip")).not.toBeNull() + document.body.click() + expect(document.body.querySelector(".tooltip")).toBeNull() + + # Tooltip can show again after hiding due to clicking outside of the tooltip + element.click() + expect(document.body.querySelector(".tooltip")).not.toBeNull() + element.click() + expect(document.body.querySelector(".tooltip")).toBeNull() + + it "allows a custom item to be specified for the content of the tooltip", -> + tooltipElement = document.createElement('div') + manager.add element, item: {element: tooltipElement} + hover element, -> + expect(tooltipElement.closest(".tooltip")).not.toBeNull() + + it "allows a custom class to be specified for the tooltip", -> + tooltipElement = document.createElement('div') + manager.add element, title: 'Title', class: 'custom-tooltip-class' + hover element, -> + expect(document.body.querySelector(".tooltip").classList.contains('custom-tooltip-class')).toBe(true) it "allows jQuery elements to be passed as the target", -> element2 = document.createElement('div') @@ -52,20 +91,6 @@ describe "TooltipManager", -> hover element, -> expect(document.body.querySelector(".tooltip")).toBeNull() hover element2, -> expect(document.body.querySelector(".tooltip")).toBeNull() - describe "when a selector is specified", -> - it "creates a tooltip when hovering over a descendant of the target that matches the selector", -> - child = document.createElement('div') - child.classList.add('bar') - grandchild = document.createElement('div') - element.appendChild(child) - child.appendChild(grandchild) - - manager.add element, selector: '.bar', title: 'Bar' - - hover grandchild, -> - expect(document.body.querySelector('.tooltip')).toHaveText('Bar') - expect(document.body.querySelector('.tooltip')).toBeNull() - describe "when a keyBindingCommand is specified", -> describe "when a title is specified", -> it "appends the key binding corresponding to the command to the title", -> diff --git a/spec/window-event-handler-spec.coffee b/spec/window-event-handler-spec.coffee index b4b8c2f73..8e08fec35 100644 --- a/spec/window-event-handler-spec.coffee +++ b/spec/window-event-handler-spec.coffee @@ -1,13 +1,10 @@ KeymapManager = require 'atom-keymap' -path = require 'path' -fs = require 'fs-plus' -temp = require 'temp' TextEditor = require '../src/text-editor' WindowEventHandler = require '../src/window-event-handler' {ipcRenderer} = require 'electron' describe "WindowEventHandler", -> - [projectPath, windowEventHandler] = [] + [windowEventHandler] = [] beforeEach -> atom.uninstallWindowEventHandler() @@ -19,7 +16,6 @@ describe "WindowEventHandler", -> loadSettings atom.project.destroy() windowEventHandler = new WindowEventHandler({atomEnvironment: atom, applicationDelegate: atom.applicationDelegate, window, document}) - projectPath = atom.project.getPaths()[0] afterEach -> windowEventHandler.unsubscribe() @@ -53,6 +49,7 @@ describe "WindowEventHandler", -> describe "beforeunload event", -> beforeEach -> jasmine.unspy(TextEditor.prototype, "shouldPromptToSave") + spyOn(atom, 'destroy') spyOn(ipcRenderer, 'send') describe "when pane items are modified", -> @@ -66,12 +63,14 @@ describe "WindowEventHandler", -> window.dispatchEvent(new CustomEvent('beforeunload')) expect(atom.workspace.confirmClose).toHaveBeenCalled() expect(ipcRenderer.send).not.toHaveBeenCalledWith('did-cancel-window-unload') + expect(atom.destroy).toHaveBeenCalled() it "cancels the unload if the user selects cancel", -> spyOn(atom.workspace, 'confirmClose').andReturn(false) window.dispatchEvent(new CustomEvent('beforeunload')) expect(atom.workspace.confirmClose).toHaveBeenCalled() expect(ipcRenderer.send).toHaveBeenCalledWith('did-cancel-window-unload') + expect(atom.destroy).not.toHaveBeenCalled() describe "when a link is clicked", -> it "opens the http/https links in an external application", -> diff --git a/spec/workspace-element-spec.coffee b/spec/workspace-element-spec.coffee index b5812b7b2..9ffa3621a 100644 --- a/spec/workspace-element-spec.coffee +++ b/spec/workspace-element-spec.coffee @@ -1,6 +1,7 @@ {ipcRenderer} = require 'electron' path = require 'path' temp = require('temp').track() +{Disposable} = require 'event-kit' describe "WorkspaceElement", -> describe "when the workspace element is focused", -> @@ -17,9 +18,11 @@ describe "WorkspaceElement", -> it "has a class based on the style of the scrollbar", -> observeCallback = null scrollbarStyle = require 'scrollbar-style' - spyOn(scrollbarStyle, 'observePreferredScrollbarStyle').andCallFake (cb) -> observeCallback = cb - workspaceElement = atom.views.getView(atom.workspace) + spyOn(scrollbarStyle, 'observePreferredScrollbarStyle').andCallFake (cb) -> + observeCallback = cb + new Disposable(->) + workspaceElement = atom.views.getView(atom.workspace) observeCallback('legacy') expect(workspaceElement.className).toMatch 'scrollbars-visible-always' diff --git a/spec/workspace-spec.coffee b/spec/workspace-spec.coffee index a9d984049..61f1e8266 100644 --- a/spec/workspace-spec.coffee +++ b/spec/workspace-spec.coffee @@ -1,8 +1,8 @@ path = require 'path' temp = require 'temp' +TextEditor = require '../src/text-editor' Workspace = require '../src/workspace' Project = require '../src/project' -Pane = require '../src/pane' platform = require './spec-helper-platform' _ = require 'underscore-plus' fstream = require 'fstream' @@ -30,7 +30,7 @@ describe "Workspace", -> atom.workspace = new Workspace({ config: atom.config, project: atom.project, packageManager: atom.packages, grammarRegistry: atom.grammars, deserializerManager: atom.deserializers, - notificationManager: atom.notifications, clipboard: atom.clipboard, + notificationManager: atom.notifications, applicationDelegate: atom.applicationDelegate, viewRegistry: atom.views, assert: atom.assert.bind(atom), textEditorRegistry: atom.textEditors @@ -81,7 +81,7 @@ describe "Workspace", -> expect(untitledEditor.getText()).toBe("An untitled editor.") expect(atom.workspace.getActiveTextEditor().getPath()).toBe editor3.getPath() - pathEscaped = escapeStringRegex(atom.project.getPaths()[0]) + pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) expect(document.title).toMatch ///^#{path.basename(editor3.getLongTitle())}\ \u2014\ #{pathEscaped}/// describe "where there are no open panes or editors", -> @@ -796,7 +796,7 @@ describe "Workspace", -> describe "::isTextEditor(obj)", -> it "returns true when the passed object is an instance of `TextEditor`", -> - expect(workspace.isTextEditor(atom.workspace.buildTextEditor())).toBe(true) + expect(workspace.isTextEditor(new TextEditor)).toBe(true) expect(workspace.isTextEditor({getText: -> null})).toBe(false) expect(workspace.isTextEditor(null)).toBe(false) expect(workspace.isTextEditor(undefined)).toBe(false) @@ -872,7 +872,7 @@ describe "Workspace", -> workspace2 = new Workspace({ config: atom.config, project: atom.project, packageManager: atom.packages, notificationManager: atom.notifications, deserializerManager: atom.deserializers, - clipboard: atom.clipboard, viewRegistry: atom.views, grammarRegistry: atom.grammars, + viewRegistry: atom.views, grammarRegistry: atom.grammars, applicationDelegate: atom.applicationDelegate, assert: atom.assert.bind(atom), textEditorRegistry: atom.textEditors }) @@ -894,28 +894,28 @@ describe "Workspace", -> describe "when there is an active pane item", -> it "sets the title to the pane item's title plus the project path", -> item = atom.workspace.getActivePaneItem() - pathEscaped = escapeStringRegex(atom.project.getPaths()[0]) + pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{pathEscaped}/// describe "when the title of the active pane item changes", -> it "updates the window title based on the item's new title", -> editor = atom.workspace.getActivePaneItem() editor.buffer.setPath(path.join(temp.dir, 'hi')) - pathEscaped = escapeStringRegex(atom.project.getPaths()[0]) + pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) expect(document.title).toMatch ///^#{editor.getTitle()}\ \u2014\ #{pathEscaped}/// describe "when the active pane's item changes", -> it "updates the title to the new item's title plus the project path", -> atom.workspace.getActivePane().activateNextItem() item = atom.workspace.getActivePaneItem() - pathEscaped = escapeStringRegex(atom.project.getPaths()[0]) + pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) expect(document.title).toMatch ///^#{item.getTitle()}\ \u2014\ #{pathEscaped}/// describe "when the last pane item is removed", -> it "updates the title to contain the project's path", -> atom.workspace.getActivePane().destroy() expect(atom.workspace.getActivePaneItem()).toBeUndefined() - pathEscaped = escapeStringRegex(atom.project.getPaths()[0]) + pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) expect(document.title).toMatch ///^#{pathEscaped}/// describe "when an inactive pane's item changes", -> @@ -935,13 +935,13 @@ describe "Workspace", -> workspace2 = new Workspace({ config: atom.config, project: atom.project, packageManager: atom.packages, notificationManager: atom.notifications, deserializerManager: atom.deserializers, - clipboard: atom.clipboard, viewRegistry: atom.views, grammarRegistry: atom.grammars, + viewRegistry: atom.views, grammarRegistry: atom.grammars, applicationDelegate: atom.applicationDelegate, assert: atom.assert.bind(atom), textEditorRegistry: atom.textEditors }) workspace2.deserialize(atom.workspace.serialize(), atom.deserializers) item = workspace2.getActivePaneItem() - pathEscaped = escapeStringRegex(atom.project.getPaths()[0]) + pathEscaped = fs.tildify(escapeStringRegex(atom.project.getPaths()[0])) expect(document.title).toMatch ///^#{item.getLongTitle()}\ \u2014\ #{pathEscaped}/// workspace2.destroy() @@ -1241,7 +1241,6 @@ describe "Workspace", -> expect(matches.length).toBe 1 it "excludes values in core.ignoredNames", -> - projectPath = path.join(__dirname, 'fixtures', 'git', 'working-dir') ignoredNames = atom.config.get("core.ignoredNames") ignoredNames.push("a") atom.config.set("core.ignoredNames", ignoredNames) @@ -1729,7 +1728,7 @@ describe "Workspace", -> describe "when there's no repository for the editor's file", -> it "doesn't do anything", -> - editor = atom.workspace.buildTextEditor() + editor = new TextEditor editor.setText("stuff") atom.workspace.checkoutHeadRevision(editor) diff --git a/src/application-delegate.coffee b/src/application-delegate.coffee index e174b2254..5d908a2c9 100644 --- a/src/application-delegate.coffee +++ b/src/application-delegate.coffee @@ -1,5 +1,5 @@ _ = require 'underscore-plus' -{screen, ipcRenderer, remote, shell, systemPreferences, webFrame} = require 'electron' +{screen, ipcRenderer, remote, shell, webFrame} = require 'electron' ipcHelpers = require './ipc-helpers' {Disposable} = require 'event-kit' {getWindowLoadSettings, setWindowLoadSettings} = require './window-load-settings-helpers' @@ -20,7 +20,7 @@ class ApplicationDelegate remote.getCurrentWindow() closeWindow: -> - ipcRenderer.send("call-window-method", "close") + ipcHelpers.call('window-method', 'close') getTemporaryWindowState: -> ipcHelpers.call('get-temporary-window-state').then (stateJSON) -> JSON.parse(stateJSON) @@ -55,72 +55,55 @@ class ApplicationDelegate ipcHelpers.call('hide-window') reloadWindow: -> - ipcRenderer.send("call-window-method", "reload") + ipcHelpers.call('window-method', 'reload') restartApplication: -> ipcRenderer.send("restart-application") minimizeWindow: -> - ipcRenderer.send("call-window-method", "minimize") + ipcHelpers.call('window-method', 'minimize') isWindowMaximized: -> remote.getCurrentWindow().isMaximized() maximizeWindow: -> - ipcRenderer.send("call-window-method", "maximize") + ipcHelpers.call('window-method', 'maximize') unmaximizeWindow: -> - ipcRenderer.send("call-window-method", "unmaximize") + ipcHelpers.call('window-method', 'unmaximize') isWindowFullScreen: -> remote.getCurrentWindow().isFullScreen() setWindowFullScreen: (fullScreen=false) -> - ipcRenderer.send("call-window-method", "setFullScreen", fullScreen) + ipcHelpers.call('window-method', 'setFullScreen', fullScreen) openWindowDevTools: -> - new Promise (resolve) -> - # Defer DevTools interaction to the next tick, because using them during - # event handling causes some wrong input events to be triggered on - # `TextEditorComponent` (Ref.: https://github.com/atom/atom/issues/9697). - process.nextTick -> - if remote.getCurrentWindow().isDevToolsOpened() - resolve() - else - remote.getCurrentWindow().once("devtools-opened", -> resolve()) - ipcRenderer.send("call-window-method", "openDevTools") + # Defer DevTools interaction to the next tick, because using them during + # event handling causes some wrong input events to be triggered on + # `TextEditorComponent` (Ref.: https://github.com/atom/atom/issues/9697). + new Promise(process.nextTick).then(-> ipcHelpers.call('window-method', 'openDevTools')) closeWindowDevTools: -> - new Promise (resolve) -> - # Defer DevTools interaction to the next tick, because using them during - # event handling causes some wrong input events to be triggered on - # `TextEditorComponent` (Ref.: https://github.com/atom/atom/issues/9697). - process.nextTick -> - unless remote.getCurrentWindow().isDevToolsOpened() - resolve() - else - remote.getCurrentWindow().once("devtools-closed", -> resolve()) - ipcRenderer.send("call-window-method", "closeDevTools") + # Defer DevTools interaction to the next tick, because using them during + # event handling causes some wrong input events to be triggered on + # `TextEditorComponent` (Ref.: https://github.com/atom/atom/issues/9697). + new Promise(process.nextTick).then(-> ipcHelpers.call('window-method', 'closeDevTools')) toggleWindowDevTools: -> - new Promise (resolve) => - # Defer DevTools interaction to the next tick, because using them during - # event handling causes some wrong input events to be triggered on - # `TextEditorComponent` (Ref.: https://github.com/atom/atom/issues/9697). - process.nextTick => - if remote.getCurrentWindow().isDevToolsOpened() - @closeWindowDevTools().then(resolve) - else - @openWindowDevTools().then(resolve) + # Defer DevTools interaction to the next tick, because using them during + # event handling causes some wrong input events to be triggered on + # `TextEditorComponent` (Ref.: https://github.com/atom/atom/issues/9697). + new Promise(process.nextTick).then(-> ipcHelpers.call('window-method', 'toggleDevTools')) executeJavaScriptInWindowDevTools: (code) -> ipcRenderer.send("execute-javascript-in-dev-tools", code) setWindowDocumentEdited: (edited) -> - ipcRenderer.send("call-window-method", "setDocumentEdited", edited) + ipcHelpers.call('window-method', 'setDocumentEdited', edited) setRepresentedFilename: (filename) -> - ipcRenderer.send("call-window-method", "setRepresentedFilename", filename) + ipcHelpers.call('window-method', 'setRepresentedFilename', filename) addRecentDocument: (filename) -> ipcRenderer.send("add-recent-document", filename) @@ -131,7 +114,7 @@ class ApplicationDelegate setWindowLoadSettings(loadSettings) setAutoHideWindowMenuBar: (autoHide) -> - ipcRenderer.send("call-window-method", "setAutoHideMenuBar", autoHide) + ipcHelpers.call('window-method', 'setAutoHideMenuBar', autoHide) setWindowMenuBarVisibility: (visible) -> remote.getCurrentWindow().setMenuBarVisibility(visible) diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index 78b65bc87..26e9b5029 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -9,10 +9,9 @@ fs = require 'fs-plus' {mapSourcePosition} = require 'source-map-support' Model = require './model' WindowEventHandler = require './window-event-handler' -StylesElement = require './styles-element' StateStore = require './state-store' StorageFolder = require './storage-folder' -{getWindowLoadSettings, setWindowLoadSettings} = require './window-load-settings-helpers' +{getWindowLoadSettings} = require './window-load-settings-helpers' registerDefaultCommands = require './register-default-commands' DeserializerManager = require './deserializer-manager' @@ -29,7 +28,6 @@ ThemeManager = require './theme-manager' MenuManager = require './menu-manager' ContextMenuManager = require './context-menu-manager' CommandInstaller = require './command-installer' -Clipboard = require './clipboard' Project = require './project' TitleBar = require './title-bar' Workspace = require './workspace' @@ -51,7 +49,6 @@ PanelElement = require './panel-element' PaneContainerElement = require './pane-container-element' PaneAxisElement = require './pane-axis-element' PaneElement = require './pane-element' -TextEditorElement = require './text-editor-element' {createGutterView} = require './gutter-component-helpers' # Essential: Atom global for dealing with packages, themes, menus, and the window. @@ -129,7 +126,7 @@ class AtomEnvironment extends Model # Call .loadOrCreate instead constructor: (params={}) -> - {@blobStore, @applicationDelegate, @window, @document, @configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params + {@blobStore, @applicationDelegate, @window, @document, @clipboard, @configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params @unloaded = false @loadTime = null @@ -156,7 +153,7 @@ class AtomEnvironment extends Model @keymaps = new KeymapManager({@configDirPath, resourcePath, notificationManager: @notifications}) - @tooltips = new TooltipManager(keymapManager: @keymaps) + @tooltips = new TooltipManager(keymapManager: @keymaps, viewRegistry: @views) @commands = new CommandRegistry @commands.attach(@window) @@ -184,20 +181,18 @@ class AtomEnvironment extends Model @packages.setContextMenuManager(@contextMenu) @packages.setThemeManager(@themes) - @clipboard = new Clipboard() - @project = new Project({notificationManager: @notifications, packageManager: @packages, @config, @applicationDelegate}) @commandInstaller = new CommandInstaller(@getVersion(), @applicationDelegate) @textEditors = new TextEditorRegistry({ - @config, grammarRegistry: @grammars, assert: @assert.bind(this), @clipboard, + @config, grammarRegistry: @grammars, assert: @assert.bind(this), packageManager: @packages }) @workspace = new Workspace({ @config, @project, packageManager: @packages, grammarRegistry: @grammars, deserializerManager: @deserializers, - notificationManager: @notifications, @applicationDelegate, @clipboard, viewRegistry: @views, assert: @assert.bind(this), + notificationManager: @notifications, @applicationDelegate, viewRegistry: @views, assert: @assert.bind(this), textEditorRegistry: @textEditors, }) @@ -850,7 +845,7 @@ class AtomEnvironment extends Model @project.addPath(selectedPath) for selectedPath in selectedPaths showSaveDialog: (callback) -> - callback(showSaveDialogSync()) + callback(@showSaveDialogSync()) showSaveDialogSync: (options={}) -> @applicationDelegate.showSaveDialog(options) diff --git a/src/block-decorations-component.coffee b/src/block-decorations-component.coffee index 35aec3921..48bbf77f3 100644 --- a/src/block-decorations-component.coffee +++ b/src/block-decorations-component.coffee @@ -24,7 +24,7 @@ class BlockDecorationsComponent @domNode.style.width = @newState.width + "px" @oldState.width = @newState.width - for id, blockDecorationState of @oldState.blockDecorations + for id of @oldState.blockDecorations unless @newState.blockDecorations.hasOwnProperty(id) blockDecorationNode = @blockDecorationNodesById[id] blockDecorationNode.previousSibling.remove() @@ -33,7 +33,7 @@ class BlockDecorationsComponent delete @blockDecorationNodesById[id] delete @oldState.blockDecorations[id] - for id, blockDecorationState of @newState.blockDecorations + for id of @newState.blockDecorations if @oldState.blockDecorations.hasOwnProperty(id) @updateBlockDecorationNode(id) else @@ -42,7 +42,6 @@ class BlockDecorationsComponent measureBlockDecorations: -> for decorationId, blockDecorationNode of @blockDecorationNodesById - style = getComputedStyle(blockDecorationNode) decoration = @newState.blockDecorations[decorationId].decoration topRuler = blockDecorationNode.previousSibling bottomRuler = blockDecorationNode.nextSibling diff --git a/src/coffee-script.js b/src/coffee-script.js index 967d07cdd..0437e787f 100644 --- a/src/coffee-script.js +++ b/src/coffee-script.js @@ -36,13 +36,10 @@ exports.compile = function (sourceCode, filePath) { var output = CoffeeScript.compile(sourceCode, { filename: filePath, sourceFiles: [filePath], - sourceMap: true + inlineMap: true }) - var js = output.js - js += '\n' - js += '//# sourceMappingURL=data:application/json;base64,' - js += new Buffer(output.v3SourceMap).toString('base64') - js += '\n' - return js + // Strip sourceURL from output so there wouldn't be duplicate entries + // in devtools. + return output.replace(/\/\/# sourceURL=[^'"\n]+\s*$/, '') } diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 955a1b540..056446203 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -214,7 +214,6 @@ class CommandRegistry immediatePropagationStopped = false matched = false currentTarget = event.target - {preventDefault, stopPropagation, stopImmediatePropagation, abortKeyBinding} = event dispatchedEvent = new CustomEvent(event.type, {bubbles: true, detail: event.detail}) Object.defineProperty dispatchedEvent, 'eventPhase', value: Event.BUBBLING_PHASE diff --git a/src/compile-cache.js b/src/compile-cache.js index 03d2376bc..ad1bd0a85 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -74,7 +74,7 @@ function compileFileAtPath (compiler, filePath, extension) { cacheStats[extension].hits++ } else { cacheStats[extension].misses++ - compiledCode = addSourceURL(compiler.compile(sourceCode, filePath), filePath) + compiledCode = compiler.compile(sourceCode, filePath) writeCachedJavascript(cachePath, compiledCode) } return compiledCode @@ -97,13 +97,6 @@ function writeCachedJavascript (relativeCachePath, code) { fs.writeFileSync(cachePath, code, 'utf8') } -function addSourceURL (jsCode, filePath) { - if (process.platform === 'win32') { - filePath = '/' + path.resolve(filePath).replace(/\\/g, '/') - } - return jsCode + '\n' + '//# sourceURL=' + encodeURI(filePath) + '\n' -} - var INLINE_SOURCE_MAP_REGEXP = /\/\/[#@]\s*sourceMappingURL=([^'"\n]+)\s*$/mg require('source-map-support').install({ diff --git a/src/config-schema.coffee b/src/config-schema.coffee index 2f5b65e33..56bdd4a99 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -12,7 +12,7 @@ module.exports = default: [".git", ".hg", ".svn", ".DS_Store", "._*", "Thumbs.db"] items: type: 'string' - description: 'List of string glob patterns. Files and directories matching these patterns will be ignored by some packages, such as the fuzzy finder and tree view. Individual packages might have additional config settings for ignoring names.' + description: 'List of [glob patterns](https://en.wikipedia.org/wiki/Glob_%28programming%29). Files and directories matching these patterns will be ignored by some packages, such as the fuzzy finder and tree view. Individual packages might have additional config settings for ignoring names.' excludeVcsIgnoredPaths: type: 'boolean' default: true diff --git a/src/config.coffee b/src/config.coffee index 1d314ada1..2dc537dd4 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -1,6 +1,6 @@ _ = require 'underscore-plus' fs = require 'fs-plus' -{CompositeDisposable, Disposable, Emitter} = require 'event-kit' +{Emitter} = require 'event-kit' CSON = require 'season' path = require 'path' async = require 'async' @@ -561,7 +561,7 @@ class Config # * `scopeDescriptor` The {ScopeDescriptor} with which the value is associated # * `value` The value for the key-path getAll: (keyPath, options) -> - {scope, sources} = options if options? + {scope} = options if options? result = [] if scope? @@ -1045,7 +1045,6 @@ class Config resetSettingsForSchemaChange: (source=@getUserConfigPath()) -> @transact => @settings = @makeValueConformToSchema(null, @settings, suppressException: true) - priority = @priorityForSource(source) selectorsAndSettings = @scopedSettingsStore.propertiesForSource(source) @scopedSettingsStore.removePropertiesForSource(source) for scopeSelector, settings of selectorsAndSettings diff --git a/src/context-menu-manager.coffee b/src/context-menu-manager.coffee index 64360dd2e..4dc54cede 100644 --- a/src/context-menu-manager.coffee +++ b/src/context-menu-manager.coffee @@ -1,4 +1,3 @@ -_ = require 'underscore-plus' path = require 'path' CSON = require 'season' fs = require 'fs-plus' diff --git a/src/cursor.coffee b/src/cursor.coffee index 1176704c9..85573f10e 100644 --- a/src/cursor.coffee +++ b/src/cursor.coffee @@ -682,7 +682,6 @@ class Cursor extends Model {row, column} = start scanRange = [[row-1, column], [0, 0]] position = new Point(0, 0) - zero = new Point(0, 0) @editor.backwardsScanInBufferRange EmptyLineRegExp, scanRange, ({range, stop}) -> position = range.start.traverse(Point(1, 0)) stop() unless position.isEqual(start) diff --git a/src/git-repository.coffee b/src/git-repository.coffee index e9d979171..d47b2e37c 100644 --- a/src/git-repository.coffee +++ b/src/git-repository.coffee @@ -1,4 +1,4 @@ -{basename, join} = require 'path' +{join} = require 'path' _ = require 'underscore-plus' {Emitter, Disposable, CompositeDisposable} = require 'event-kit' diff --git a/src/grammar-registry.coffee b/src/grammar-registry.coffee index 1876e1d74..899bb4cff 100644 --- a/src/grammar-registry.coffee +++ b/src/grammar-registry.coffee @@ -1,5 +1,4 @@ _ = require 'underscore-plus' -{Emitter} = require 'event-kit' FirstMate = require 'first-mate' Token = require './token' fs = require 'fs-plus' diff --git a/src/gutter-container-component.coffee b/src/gutter-container-component.coffee index c5538324b..56b0fea84 100644 --- a/src/gutter-container-component.coffee +++ b/src/gutter-container-component.coffee @@ -18,7 +18,7 @@ class GutterContainerComponent @domNode.style.display = 'flex' destroy: -> - for {name, component} in @gutterComponents + for {component} in @gutterComponents component.destroy?() return diff --git a/src/initialize-application-window.coffee b/src/initialize-application-window.coffee index 04784efa0..585ec93f7 100644 --- a/src/initialize-application-window.coffee +++ b/src/initialize-application-window.coffee @@ -5,7 +5,7 @@ module.exports = ({blobStore}) -> require './window' {getWindowLoadSettings} = require './window-load-settings-helpers' {ipcRenderer} = require 'electron' - {resourcePath, isSpec, devMode, env} = getWindowLoadSettings() + {resourcePath, devMode, env} = getWindowLoadSettings() require '../src/electron-shims' updateProcessEnv(env) @@ -20,16 +20,21 @@ module.exports = ({blobStore}) -> AtomEnvironment = require './atom-environment' ApplicationDelegate = require './application-delegate' + Clipboard = require './clipboard' + TextEditor = require './text-editor' + + clipboard = new Clipboard + TextEditor.setClipboard(clipboard) + window.atom = new AtomEnvironment({ - window, document, blobStore, + window, document, clipboard, blobStore, applicationDelegate: new ApplicationDelegate, - configDirPath: process.env.ATOM_HOME - enablePersistence: true + configDirPath: process.env.ATOM_HOME, + enablePersistence: true, env: process.env }) atom.startEditorWindow().then -> - # Workaround for focus getting cleared upon window creation windowFocused = -> window.removeEventListener('focus', windowFocused) diff --git a/src/initialize-benchmark-window.js b/src/initialize-benchmark-window.js new file mode 100644 index 000000000..e4be4420b --- /dev/null +++ b/src/initialize-benchmark-window.js @@ -0,0 +1,111 @@ +/** @babel */ + +import {remote} from 'electron' +import path from 'path' +import ipcHelpers from './ipc-helpers' +import util from 'util' + +export default async function () { + const {getWindowLoadSettings} = require('./window-load-settings-helpers') + const {test, headless, resourcePath, benchmarkPaths} = getWindowLoadSettings() + try { + const Clipboard = require('../src/clipboard') + const ApplicationDelegate = require('../src/application-delegate') + const AtomEnvironment = require('../src/atom-environment') + const TextEditor = require('../src/text-editor') + + const exportsPath = path.join(resourcePath, 'exports') + require('module').globalPaths.push(exportsPath) // Add 'exports' to module search path. + process.env.NODE_PATH = exportsPath // Set NODE_PATH env variable since tasks may need it. + + document.title = 'Benchmarks' + // Allow `document.title` to be assigned in benchmarks without actually changing the window title. + let documentTitle = null + Object.defineProperty(document, 'title', { + get () { return documentTitle }, + set (title) { documentTitle = title } + }) + + window.addEventListener('keydown', (event) => { + // Reload: cmd-r / ctrl-r + if ((event.metaKey || event.ctrlKey) && event.keyCode === 82) { + ipcHelpers.call('window-method', 'reload') + } + + // Toggle Dev Tools: cmd-alt-i (Mac) / ctrl-shift-i (Linux/Windows) + if (event.keyCode === 73) { + const isDarwin = process.platform === 'darwin' + if ((isDarwin && event.metaKey && event.altKey) || (!isDarwin && event.ctrlKey && event.shiftKey)) { + ipcHelpers.call('window-method', 'toggleDevTools') + } + } + + // Close: cmd-w / ctrl-w + if ((event.metaKey || event.ctrlKey) && event.keyCode === 87) { + ipcHelpers.call('window-method', 'close') + } + + // Copy: cmd-c / ctrl-c + if ((event.metaKey || event.ctrlKey) && event.keyCode === 67) { + ipcHelpers.call('window-method', 'copy') + } + }, true) + + const clipboard = new Clipboard() + TextEditor.setClipboard(clipboard) + + const applicationDelegate = new ApplicationDelegate() + global.atom = new AtomEnvironment({ + applicationDelegate, + window, + document, + clipboard, + configDirPath: process.env.ATOM_HOME, + enablePersistence: false + }) + + // Prevent benchmarks from modifying application menus + global.atom.menu.sendToBrowserProcess = function () { } + + if (headless) { + Object.defineProperties(process, { + stdout: { value: remote.process.stdout }, + stderr: { value: remote.process.stderr } + }) + + console.log = function (...args) { + const formatted = util.format(...args) + process.stdout.write(formatted + '\n') + } + console.warn = function (...args) { + const formatted = util.format(...args) + process.stderr.write(formatted + '\n') + } + console.error = function (...args) { + const formatted = util.format(...args) + process.stderr.write(formatted + '\n') + } + } else { + remote.getCurrentWindow().show() + } + + const benchmarkRunner = require('../benchmarks/benchmark-runner') + const statusCode = await benchmarkRunner({test, benchmarkPaths}) + if (headless) { + exitWithStatusCode(statusCode) + } + } catch (error) { + if (headless) { + console.error(error.stack || error) + exitWithStatusCode(1) + } else { + ipcHelpers.call('window-method', 'openDevTools') + throw error + } + } +} + +function exitWithStatusCode (statusCode) { + remote.app.emit('will-quit') + remote.process.exit(statusCode) +} diff --git a/src/initialize-test-window.coffee b/src/initialize-test-window.coffee index a775ec3fb..e4706fe1d 100644 --- a/src/initialize-test-window.coffee +++ b/src/initialize-test-window.coffee @@ -1,3 +1,5 @@ +ipcHelpers = require './ipc-helpers' + cloneObject = (object) -> clone = {} clone[key] = value for key, value of object @@ -19,6 +21,8 @@ module.exports = ({blobStore}) -> {getWindowLoadSettings} = require './window-load-settings-helpers' AtomEnvironment = require '../src/atom-environment' ApplicationDelegate = require '../src/application-delegate' + Clipboard = require '../src/clipboard' + TextEditor = require '../src/text-editor' require '../src/electron-shims' {testRunnerPath, legacyTestRunnerPath, headless, logFile, testPaths} = getWindowLoadSettings() @@ -31,19 +35,21 @@ module.exports = ({blobStore}) -> handleKeydown = (event) -> # Reload: cmd-r / ctrl-r if (event.metaKey or event.ctrlKey) and event.keyCode is 82 - ipcRenderer.send('call-window-method', 'reload') + ipcHelpers.call('window-method', 'reload') - # Toggle Dev Tools: cmd-alt-i / ctrl-alt-i - if (event.metaKey or event.ctrlKey) and event.altKey and event.keyCode is 73 - ipcRenderer.send('call-window-method', 'toggleDevTools') + # Toggle Dev Tools: cmd-alt-i (Mac) / ctrl-shift-i (Linux/Windows) + if event.keyCode is 73 and ( + (process.platform is 'darwin' and event.metaKey and event.altKey) or + (process.platform isnt 'darwin' and event.ctrlKey and event.shiftKey)) + ipcHelpers.call('window-method', 'toggleDevTools') # Close: cmd-w / ctrl-w if (event.metaKey or event.ctrlKey) and event.keyCode is 87 - ipcRenderer.send('call-window-method', 'close') + ipcHelpers.call('window-method', 'close') # Copy: cmd-c / ctrl-c if (event.metaKey or event.ctrlKey) and event.keyCode is 67 - ipcRenderer.send('call-window-method', 'copy') + ipcHelpers.call('window-method', 'copy') window.addEventListener('keydown', handleKeydown, true) @@ -54,11 +60,15 @@ module.exports = ({blobStore}) -> document.title = "Spec Suite" + clipboard = new Clipboard + TextEditor.setClipboard(clipboard) + testRunner = require(testRunnerPath) legacyTestRunner = require(legacyTestRunnerPath) buildDefaultApplicationDelegate = -> new ApplicationDelegate() buildAtomEnvironment = (params) -> params = cloneObject(params) + params.clipboard = clipboard unless params.hasOwnProperty("clipboard") params.blobStore = blobStore unless params.hasOwnProperty("blobStore") params.onlyLoadBaseStyleSheets = true unless params.hasOwnProperty("onlyLoadBaseStyleSheets") new AtomEnvironment(params) diff --git a/src/ipc-helpers.js b/src/ipc-helpers.js index 385158793..6a7565968 100644 --- a/src/ipc-helpers.js +++ b/src/ipc-helpers.js @@ -12,12 +12,12 @@ exports.on = function (emitter, eventName, callback) { }) } -exports.call = function (methodName, ...args) { +exports.call = function (channel, ...args) { if (!ipcRenderer) { ipcRenderer = require('electron').ipcRenderer } - var responseChannel = getResponseChannel(methodName) + var responseChannel = getResponseChannel(channel) return new Promise(function (resolve) { ipcRenderer.on(responseChannel, function (event, result) { @@ -25,26 +25,26 @@ exports.call = function (methodName, ...args) { resolve(result) }) - ipcRenderer.send(methodName, ...args) + ipcRenderer.send(channel, ...args) }) } -exports.respondTo = function (methodName, callback) { +exports.respondTo = function (channel, callback) { if (!ipcMain) { var electron = require('electron') ipcMain = electron.ipcMain BrowserWindow = electron.BrowserWindow } - var responseChannel = getResponseChannel(methodName) + var responseChannel = getResponseChannel(channel) - return exports.on(ipcMain, methodName, function (event, ...args) { + return exports.on(ipcMain, channel, function (event, ...args) { var browserWindow = BrowserWindow.fromWebContents(event.sender) var result = callback(browserWindow, ...args) event.sender.send(responseChannel, result) }) } -function getResponseChannel (methodName) { - return 'ipc-helpers-' + methodName + '-response' +function getResponseChannel (channel) { + return 'ipc-helpers-' + channel + '-response' } diff --git a/src/language-mode.coffee b/src/language-mode.coffee index a3ba2e664..bb9f339c4 100644 --- a/src/language-mode.coffee +++ b/src/language-mode.coffee @@ -2,6 +2,7 @@ _ = require 'underscore-plus' {OnigRegExp} = require 'oniguruma' ScopeDescriptor = require './scope-descriptor' +NullGrammar = require './null-grammar' module.exports = class LanguageMode @@ -147,19 +148,19 @@ class LanguageMode rowRange rowRangeForCommentAtBufferRow: (bufferRow) -> - return unless @editor.tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment() + return unless @editor.tokenizedBuffer.tokenizedLines[bufferRow]?.isComment() startRow = bufferRow endRow = bufferRow if bufferRow > 0 for currentRow in [bufferRow-1..0] by -1 - break unless @editor.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment() + break unless @editor.tokenizedBuffer.tokenizedLines[currentRow]?.isComment() startRow = currentRow if bufferRow < @buffer.getLastRow() for currentRow in [bufferRow+1..@buffer.getLastRow()] by 1 - break unless @editor.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment() + break unless @editor.tokenizedBuffer.tokenizedLines[currentRow]?.isComment() endRow = currentRow return [startRow, endRow] if startRow isnt endRow @@ -188,7 +189,7 @@ class LanguageMode # row is a comment. isLineCommentedAtBufferRow: (bufferRow) -> return false unless 0 <= bufferRow <= @editor.getLastBufferRow() - @editor.tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment() + @editor.tokenizedBuffer.tokenizedLines[bufferRow]?.isComment() # Find a row range for a 'paragraph' around specified bufferRow. A paragraph # is a block of text bounded by and empty line or a block of text that is not @@ -253,7 +254,6 @@ class LanguageMode iterator.next() scopeDescriptor = new ScopeDescriptor(scopes: iterator.getScopes()) - patterns = increaseIndentRegex = @increaseIndentRegexForScopeDescriptor(scopeDescriptor) decreaseIndentRegex = @decreaseIndentRegexForScopeDescriptor(scopeDescriptor) decreaseNextIndentRegex = @decreaseNextIndentRegexForScopeDescriptor(scopeDescriptor) diff --git a/src/layer-decoration.coffee b/src/layer-decoration.coffee index e00e024cb..fb544948f 100644 --- a/src/layer-decoration.coffee +++ b/src/layer-decoration.coffee @@ -1,5 +1,3 @@ -_ = require 'underscore-plus' - idCounter = 0 nextId = -> idCounter++ diff --git a/src/line-number-gutter-component.coffee b/src/line-number-gutter-component.coffee index 3a3c199c2..b74747770 100644 --- a/src/line-number-gutter-component.coffee +++ b/src/line-number-gutter-component.coffee @@ -1,7 +1,5 @@ TiledComponent = require './tiled-component' LineNumbersTileComponent = require './line-numbers-tile-component' -WrapperDiv = document.createElement('div') -DOMElementPool = require './dom-element-pool' module.exports = class LineNumberGutterComponent extends TiledComponent diff --git a/src/line-numbers-tile-component.coffee b/src/line-numbers-tile-component.coffee index 30f13fff2..d552e2056 100644 --- a/src/line-numbers-tile-component.coffee +++ b/src/line-numbers-tile-component.coffee @@ -96,7 +96,7 @@ class LineNumbersTileComponent screenRowForNode: (node) -> parseInt(node.dataset.screenRow) buildLineNumberNode: (lineNumberState) -> - {screenRow, bufferRow, softWrapped, top, decorationClasses, zIndex, blockDecorationsHeight} = lineNumberState + {screenRow, bufferRow, softWrapped, blockDecorationsHeight} = lineNumberState className = @buildLineNumberClassName(lineNumberState) lineNumberNode = @domElementPool.buildElement("div", className) diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 2ef5fcdc4..fb9b28b03 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -15,8 +15,6 @@ DummyLineNode.children[1].textContent = 'ꈑ' DummyLineNode.children[2].textContent = 'ハ' DummyLineNode.children[3].textContent = '세' -RangeForMeasurement = document.createRange() - module.exports = class LinesComponent extends TiledComponent placeholderTextDiv: null @@ -77,7 +75,6 @@ class LinesComponent extends TiledComponent measureLineHeightAndDefaultCharWidth: -> @domNode.appendChild(DummyLineNode) - textNode = DummyLineNode.firstChild.childNodes[0] lineHeightInPixels = DummyLineNode.getBoundingClientRect().height defaultCharWidth = DummyLineNode.children[0].getBoundingClientRect().width diff --git a/src/lines-tile-component.coffee b/src/lines-tile-component.coffee index 64c1e59bd..c1cb2ba64 100644 --- a/src/lines-tile-component.coffee +++ b/src/lines-tile-component.coffee @@ -1,9 +1,4 @@ -_ = require 'underscore-plus' - HighlightsComponent = require './highlights-component' -AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT} -TokenTextEscapeRegex = /[&"'<>]/g -MaxTokenLength = 20000 ZERO_WIDTH_NBSP = '\ufeff' cloneObject = (object) -> @@ -201,7 +196,6 @@ class LinesTileComponent lineNode.classList.add(decorationClass) textNodes = [] - lineLength = 0 startIndex = 0 openScopeNode = lineNode for tagCode in tagCodes when tagCode isnt 0 diff --git a/src/lines-yardstick.coffee b/src/lines-yardstick.coffee index 13da96fa1..d4979865c 100644 --- a/src/lines-yardstick.coffee +++ b/src/lines-yardstick.coffee @@ -13,20 +13,21 @@ class LinesYardstick measuredRowForPixelPosition: (pixelPosition) -> targetTop = pixelPosition.top row = Math.floor(targetTop / @model.getLineHeightInPixels()) - row if 0 <= row <= @model.getLastScreenRow() + row if 0 <= row screenPositionForPixelPosition: (pixelPosition) -> targetTop = pixelPosition.top - targetLeft = pixelPosition.left - defaultCharWidth = @model.getDefaultCharWidth() - row = @lineTopIndex.rowForPixelPosition(targetTop) - targetLeft = 0 if targetTop < 0 or targetLeft < 0 - targetLeft = Infinity if row > @model.getLastScreenRow() - row = Math.min(row, @model.getLastScreenRow()) - row = Math.max(0, row) - + row = Math.max(0, @lineTopIndex.rowForPixelPosition(targetTop)) lineNode = @lineNodesProvider.lineNodeForScreenRow(row) - return Point(row, 0) unless lineNode + unless lineNode + lastScreenRow = @model.getLastScreenRow() + if row > lastScreenRow + return Point(lastScreenRow, @model.lineLengthForScreenRow(lastScreenRow)) + else + return Point(row, 0) + + targetLeft = pixelPosition.left + targetLeft = 0 if targetTop < 0 or targetLeft < 0 textNodes = @lineNodesProvider.textNodesForScreenRow(row) lineOffset = lineNode.getBoundingClientRect().left @@ -91,34 +92,34 @@ class LinesYardstick lineNode = @lineNodesProvider.lineNodeForScreenRow(row) lineId = @lineNodesProvider.lineIdForScreenRow(row) - return 0 unless lineNode? - - if cachedPosition = @leftPixelPositionCache[lineId]?[column] - return cachedPosition - - textNodes = @lineNodesProvider.textNodesForScreenRow(row) - textNodeStartColumn = 0 - - for textNode in textNodes - textNodeEndColumn = textNodeStartColumn + textNode.textContent.length - if textNodeEndColumn > column - indexInTextNode = column - textNodeStartColumn - break + if lineNode? + if @leftPixelPositionCache[lineId]?[column]? + @leftPixelPositionCache[lineId][column] else - textNodeStartColumn = textNodeEndColumn + textNodes = @lineNodesProvider.textNodesForScreenRow(row) + textNodeStartColumn = 0 + for textNode in textNodes + textNodeEndColumn = textNodeStartColumn + textNode.textContent.length + if textNodeEndColumn > column + indexInTextNode = column - textNodeStartColumn + break + else + textNodeStartColumn = textNodeEndColumn - if textNode? - indexInTextNode ?= textNode.textContent.length - lineOffset = lineNode.getBoundingClientRect().left - if indexInTextNode is 0 - leftPixelPosition = @clientRectForRange(textNode, 0, 1).left - else - leftPixelPosition = @clientRectForRange(textNode, 0, indexInTextNode).right - leftPixelPosition -= lineOffset + if textNode? + indexInTextNode ?= textNode.textContent.length + lineOffset = lineNode.getBoundingClientRect().left + if indexInTextNode is 0 + leftPixelPosition = @clientRectForRange(textNode, 0, 1).left + else + leftPixelPosition = @clientRectForRange(textNode, 0, indexInTextNode).right + leftPixelPosition -= lineOffset - @leftPixelPositionCache[lineId] ?= {} - @leftPixelPositionCache[lineId][column] = leftPixelPosition - leftPixelPosition + @leftPixelPositionCache[lineId] ?= {} + @leftPixelPositionCache[lineId][column] = leftPixelPosition + leftPixelPosition + else + 0 else 0 diff --git a/src/main-process/atom-application.coffee b/src/main-process/atom-application.coffee index 7aa0fd1cc..e829b32bc 100644 --- a/src/main-process/atom-application.coffee +++ b/src/main-process/atom-application.coffee @@ -33,7 +33,8 @@ class AtomApplication @open: (options) -> unless options.socketPath? if process.platform is 'win32' - options.socketPath = "\\\\.\\pipe\\atom-#{options.version}-sock" + userNameSafe = new Buffer(process.env.USERNAME).toString('base64') + options.socketPath = "\\\\.\\pipe\\atom-#{options.version}-#{userNameSafe}-sock" else options.socketPath = path.join(os.tmpdir(), "atom-#{options.version}-#{process.env.USER}.sock") @@ -41,7 +42,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 (process.platform isnt 'win32' and not fs.existsSync options.socketPath) or options.test + if (process.platform isnt 'win32' and not fs.existsSync options.socketPath) or options.test or options.benchmark or options.benchmarkTest new AtomApplication(options).initialize(options) return @@ -62,8 +63,8 @@ class AtomApplication exit: (status) -> app.exit(status) constructor: (options) -> - {@resourcePath, @devResourcePath, @version, @devMode, @safeMode, @socketPath, @logFile, @setPortable, @userDataDir, timeout, clearWindowState} = options - @socketPath = null if options.test + {@resourcePath, @devResourcePath, @version, @devMode, @safeMode, @socketPath, @logFile, @setPortable, @userDataDir} = options + @socketPath = null if options.test or options.benchmark or options.benchmarkTest @pidsToOpenWindows = {} @windows = [] @@ -83,9 +84,15 @@ class AtomApplication initialize: (options) -> global.atomApplication = this +<<<<<<< HEAD @config.onDidChange 'core.titleBar', @promptForRestart +======= + @config.onDidChange 'core.useCustomTitleBar', @promptForRestart.bind(this) +>>>>>>> master - @autoUpdateManager = new AutoUpdateManager(@version, options.test, @resourcePath, @config) + @autoUpdateManager = new AutoUpdateManager( + @version, options.test or options.benchmark or options.benchmarkTest, @resourcePath, @config + ) @applicationMenu = new ApplicationMenu(@version, @autoUpdateManager) @atomProtocolHandler = new AtomProtocolHandler(@resourcePath, @safeMode) @@ -102,23 +109,41 @@ class AtomApplication Promise.all(windowsClosePromises).then(=> @disposable.dispose()) launch: (options) -> - if options.pathsToOpen?.length > 0 or options.urlsToOpen?.length > 0 or options.test + if options.pathsToOpen?.length > 0 or options.urlsToOpen?.length > 0 or options.test or options.benchmark or options.benchmarkTest @openWithOptions(options) else @loadState(options) or @openPath(options) - openWithOptions: ({initialPaths, pathsToOpen, executedFrom, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, profileStartup, timeout, clearWindowState, addToLastWindow, env}) -> + openWithOptions: (options) -> + { + initialPaths, pathsToOpen, executedFrom, urlsToOpen, benchmark, + benchmarkTest, test, pidToKillWhenClosed, devMode, safeMode, newWindow, + logFile, profileStartup, timeout, clearWindowState, addToLastWindow, env + } = options + app.focus() if test - @runTests({headless: true, devMode, @resourcePath, executedFrom, pathsToOpen, logFile, timeout, env}) + @runTests({ + headless: true, devMode, @resourcePath, executedFrom, pathsToOpen, + logFile, timeout, env + }) + else if benchmark or benchmarkTest + @runBenchmarks({headless: true, test: benchmarkTest, @resourcePath, executedFrom, pathsToOpen, timeout, env}) else if pathsToOpen.length > 0 - @openPaths({initialPaths, pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, clearWindowState, addToLastWindow, env}) + @openPaths({ + initialPaths, pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, + devMode, safeMode, profileStartup, clearWindowState, addToLastWindow, env + }) else if urlsToOpen.length > 0 - @openUrl({urlToOpen, devMode, safeMode, env}) for urlToOpen in urlsToOpen + for urlToOpen in urlsToOpen + @openUrl({urlToOpen, devMode, safeMode, env}) else # Always open a editor window if this is the first instance of Atom. - @openPath({initialPaths, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, clearWindowState, addToLastWindow, env}) + @openPath({ + initialPaths, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, + clearWindowState, addToLastWindow, env + }) # Public: Removes the {AtomWindow} from the global window list. removeWindow: (window) -> @@ -250,16 +275,17 @@ class AtomApplication event.preventDefault() @openUrl({urlToOpen, @devMode, @safeMode}) - @disposable.add ipcHelpers.on app, 'activate-with-no-open-windows', (event) => - event?.preventDefault() - @emit('application:new-window') + @disposable.add ipcHelpers.on app, 'activate', (event, hasVisibleWindows) => + unless hasVisibleWindows + event?.preventDefault() + @emit('application:new-window') @disposable.add ipcHelpers.on ipcMain, 'restart-application', => @restart() # A request from the associated render process to open a new render process. @disposable.add ipcHelpers.on ipcMain, 'open', (event, options) => - window = @windowForEvent(event) + window = @atomWindowForEvent(event) if options? if typeof options.pathsToOpen is 'string' options.pathsToOpen = [options.pathsToOpen] @@ -278,6 +304,9 @@ class AtomApplication @disposable.add ipcHelpers.on ipcMain, 'run-package-specs', (event, packageSpecPath) => @runTests({resourcePath: @devResourcePath, pathsToOpen: [packageSpecPath], headless: false}) + @disposable.add ipcHelpers.on ipcMain, 'run-benchmarks', (event, benchmarksPath) => + @runBenchmarks({resourcePath: @devResourcePath, pathsToOpen: [benchmarksPath], headless: false, test: false}) + @disposable.add ipcHelpers.on ipcMain, 'command', (event, command) => @emit(command) @@ -293,9 +322,8 @@ class AtomApplication win = BrowserWindow.fromWebContents(event.sender) win.emit(command, args...) - @disposable.add ipcHelpers.on ipcMain, 'call-window-method', (event, method, args...) -> - win = BrowserWindow.fromWebContents(event.sender) - win[method](args...) + @disposable.add ipcHelpers.respondTo 'window-method', (browserWindow, method, args...) => + @atomWindowForBrowserWindow(browserWindow)?[method](args...) @disposable.add ipcHelpers.on ipcMain, 'pick-folder', (event, responseChannel) => @promptForPath "folder", (selectedPaths) -> @@ -353,11 +381,11 @@ class AtomApplication event.returnValue = @autoUpdateManager.getErrorMessage() @disposable.add ipcHelpers.on ipcMain, 'will-save-path', (event, path) => - @fileRecoveryService.willSavePath(@windowForEvent(event), path) + @fileRecoveryService.willSavePath(@atomWindowForEvent(event), path) event.returnValue = true @disposable.add ipcHelpers.on ipcMain, 'did-save-path', (event, path) => - @fileRecoveryService.didSavePath(@windowForEvent(event), path) + @fileRecoveryService.didSavePath(@atomWindowForEvent(event), path) event.returnValue = true setupDockMenu: -> @@ -428,9 +456,11 @@ class AtomApplication atomWindow.devMode is devMode and atomWindow.containsPaths(pathsToOpen) # Returns the {AtomWindow} for the given ipcMain event. - windowForEvent: ({sender}) -> - window = BrowserWindow.fromWebContents(sender) - _.find @windows, ({browserWindow}) -> window is browserWindow + atomWindowForEvent: ({sender}) -> + @atomWindowForBrowserWindow(BrowserWindow.fromWebContents(sender)) + + atomWindowForBrowserWindow: (browserWindow) -> + @windows.find((atomWindow) -> atomWindow.browserWindow is browserWindow) # Public: Returns the currently focused {AtomWindow} or undefined if none. focusedWindow: -> @@ -648,6 +678,29 @@ class AtomApplication safeMode ?= false new AtomWindow(this, @fileRecoveryService, {windowInitializationScript, resourcePath, headless, isSpec, devMode, testRunnerPath, legacyTestRunnerPath, testPaths, logFile, safeMode, env}) + runBenchmarks: ({headless, test, resourcePath, executedFrom, pathsToOpen, env}) -> + if resourcePath isnt @resourcePath and not fs.existsSync(resourcePath) + resourcePath = @resourcePath + + try + windowInitializationScript = require.resolve(path.resolve(@devResourcePath, 'src', 'initialize-benchmark-window')) + catch error + windowInitializationScript = require.resolve(path.resolve(__dirname, '..', '..', 'src', 'initialize-benchmark-window')) + + benchmarkPaths = [] + if pathsToOpen? + for pathToOpen in pathsToOpen + benchmarkPaths.push(path.resolve(executedFrom, fs.normalize(pathToOpen))) + + if benchmarkPaths.length is 0 + process.stderr.write 'Error: Specify at least one benchmark path.\n\n' + process.exit(1) + + devMode = true + isSpec = true + safeMode = false + new AtomWindow(this, @fileRecoveryService, {windowInitializationScript, resourcePath, headless, test, isSpec, devMode, benchmarkPaths, safeMode, env}) + resolveTestRunnerPath: (testPath) -> FindParentDir ?= require 'find-parent-dir' diff --git a/src/main-process/atom-protocol-handler.coffee b/src/main-process/atom-protocol-handler.coffee index 3967c0525..db385b4b7 100644 --- a/src/main-process/atom-protocol-handler.coffee +++ b/src/main-process/atom-protocol-handler.coffee @@ -1,4 +1,4 @@ -{app, protocol} = require 'electron' +{protocol} = require 'electron' fs = require 'fs' path = require 'path' diff --git a/src/main-process/atom-window.coffee b/src/main-process/atom-window.coffee index b6c69a339..6736089e7 100644 --- a/src/main-process/atom-window.coffee +++ b/src/main-process/atom-window.coffee @@ -16,7 +16,7 @@ class AtomWindow isSpec: null constructor: (@atomApplication, @fileRecoveryService, settings={}) -> - {@resourcePath, initialPaths, pathToOpen, locationsToOpen, @isSpec, @headless, @safeMode, @devMode} = settings + {@resourcePath, pathToOpen, locationsToOpen, @isSpec, @headless, @safeMode, @devMode} = settings locationsToOpen ?= [{pathToOpen}] if pathToOpen locationsToOpen ?= [] @@ -77,8 +77,7 @@ class AtomWindow @browserWindow.loadSettings = loadSettings - @browserWindow.once 'window:loaded', => - @loaded = true + @browserWindow.on 'window:loaded', => @emit 'window:loaded' @resolveLoadedPromise() @@ -197,10 +196,7 @@ class AtomWindow @openLocations([{pathToOpen, initialLine, initialColumn}]) openLocations: (locationsToOpen) -> - if @loaded - @sendMessage 'open-locations', locationsToOpen - else - @browserWindow.once 'window:loaded', => @openLocations(locationsToOpen) + @loadedPromise.then => @sendMessage 'open-locations', locationsToOpen replaceEnvironment: (env) -> @browserWindow.webContents.send 'environment', env @@ -248,8 +244,14 @@ class AtomWindow maximize: -> @browserWindow.maximize() + unmaximize: -> @browserWindow.unmaximize() + restore: -> @browserWindow.restore() + setFullScreen: (fullScreen) -> @browserWindow.setFullScreen(fullScreen) + + setAutoHideMenuBar: (autoHideMenuBar) -> @browserWindow.setAutoHideMenuBar(autoHideMenuBar) + handlesAtomCommands: -> not @isSpecWindow() and @isWebViewFocused() @@ -263,6 +265,19 @@ class AtomWindow isSpecWindow: -> @isSpec - reload: -> @browserWindow.reload() + reload: -> + @loadedPromise = new Promise((@resolveLoadedPromise) =>) + @saveState().then => @browserWindow.reload() + @loadedPromise toggleDevTools: -> @browserWindow.toggleDevTools() + + openDevTools: -> @browserWindow.openDevTools() + + closeDevTools: -> @browserWindow.closeDevTools() + + setDocumentEdited: (documentEdited) -> @browserWindow.setDocumentEdited(documentEdited) + + setRepresentedFilename: (representedFilename) -> @browserWindow.setRepresentedFilename(representedFilename) + + copy: -> @browserWindow.copy() diff --git a/src/main-process/main.js b/src/main-process/main.js index dca2bc2e7..7ccd1a6c3 100644 --- a/src/main-process/main.js +++ b/src/main-process/main.js @@ -1,115 +1,36 @@ -global.shellStartTime = Date.now() +const startTime = Date.now() -process.on('uncaughtException', function (error = {}) { - if (error.message != null) { - console.log(error.message) - } - - if (error.stack != null) { - console.log(error.stack) - } -}) - -const {app} = require('electron') -const fs = require('fs-plus') +const electron = require('electron') +const fs = require('fs') const path = require('path') -const temp = require('temp') -const parseCommandLine = require('./parse-command-line') -const startCrashReporter = require('../crash-reporter-start') -const previousConsoleLog = console.log -console.log = require('nslog') +const yargs = require('yargs') -function start () { - const args = parseCommandLine(process.argv.slice(1)) - setupAtomHome(args) - setupCompileCache() +const args = + yargs(process.argv) + .alias('d', 'dev') + .alias('t', 'test') + .argv - if (handleStartupEventWithSquirrel()) { - return - } else if (args.test && args.mainProcess) { - app.setPath('userData', temp.mkdirSync('atom-user-data-dir-for-main-process-tests')) - console.log = previousConsoleLog - app.on('ready', function () { - const testRunner = require(path.join(args.resourcePath, 'spec/main-process/mocha-test-runner')) - testRunner(args.pathsToOpen) - }) - return - } +let resourcePath - // NB: This prevents Win10 from showing dupe items in the taskbar - app.setAppUserModelId('com.squirrel.atom.atom') +if (args.resourcePath) { + resourcePath = args.resourcePath +} else { + const stableResourcePath = path.dirname(path.dirname(__dirname)) + const defaultRepositoryPath = path.join(electron.app.getPath('home'), 'github', 'atom') - function addPathToOpen (event, pathToOpen) { - event.preventDefault() - args.pathsToOpen.push(pathToOpen) - } - - function addUrlToOpen (event, urlToOpen) { - event.preventDefault() - args.urlsToOpen.push(urlToOpen) - } - - app.on('open-file', addPathToOpen) - app.on('open-url', addUrlToOpen) - app.on('will-finish-launching', startCrashReporter) - - if (args.userDataDir != null) { - app.setPath('userData', args.userDataDir) - } else if (args.test) { - app.setPath('userData', temp.mkdirSync('atom-test-data')) - } - - app.on('ready', function () { - app.removeListener('open-file', addPathToOpen) - app.removeListener('open-url', addUrlToOpen) - const AtomApplication = require(path.join(args.resourcePath, 'src', 'main-process', 'atom-application')) - AtomApplication.open(args) - }) -} - -function handleStartupEventWithSquirrel () { - if (process.platform !== 'win32') { - return false - } - - const SquirrelUpdate = require('./squirrel-update') - const squirrelCommand = process.argv[1] - return SquirrelUpdate.handleStartupEvent(app, squirrelCommand) -} - -function setupAtomHome ({setPortable}) { - if (process.env.ATOM_HOME) { - return - } - - let atomHome = path.join(app.getPath('home'), '.atom') - const AtomPortable = require('./atom-portable') - - if (setPortable && !AtomPortable.isPortableInstall(process.platform, process.env.ATOM_HOME, atomHome)) { - try { - AtomPortable.setPortable(atomHome) - } catch (error) { - console.log(`Failed copying portable directory '${atomHome}' to '${AtomPortable.getPortableAtomHomePath()}'`) - console.log(`${error.message} ${error.stack}`) + if (args.dev || args.test || args.benchmark || args.benchmarkTest) { + if (process.env.ATOM_DEV_RESOURCE_PATH) { + resourcePath = process.env.ATOM_DEV_RESOURCE_PATH + } else if (fs.statSyncNoException(defaultRepositoryPath)) { + resourcePath = defaultRepositoryPath + } else { + resourcePath = stableResourcePath } + } else { + resourcePath = stableResourcePath } - - if (AtomPortable.isPortableInstall(process.platform, process.env.ATOM_HOME, atomHome)) { - atomHome = AtomPortable.getPortableAtomHomePath() - } - - try { - atomHome = fs.realpathSync(atomHome) - } catch (e) { - // Don't throw an error if atomHome doesn't exist. - } - - process.env.ATOM_HOME = atomHome } -function setupCompileCache () { - const CompileCache = require('../compile-cache') - CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME) -} - -start() +const start = require(path.join(resourcePath, 'src', 'main-process', 'start')) +start(resourcePath, startTime) diff --git a/src/main-process/parse-command-line.js b/src/main-process/parse-command-line.js index 8b2d8b811..68a18fa30 100644 --- a/src/main-process/parse-command-line.js +++ b/src/main-process/parse-command-line.js @@ -7,7 +7,7 @@ const path = require('path') const fs = require('fs-plus') module.exports = function parseCommandLine (processArgs) { - const options = yargs(processArgs).wrap(100) + const options = yargs(processArgs).wrap(yargs.terminalWidth()) const version = app.getVersion() options.usage( dedent`Atom Editor v${version} @@ -45,6 +45,8 @@ module.exports = function parseCommandLine (processArgs) { 'portable', 'Set portable mode. Copies the ~/.atom folder to be a sibling of the installed Atom location if a .atom folder is not already there.' ) + options.boolean('benchmark').describe('benchmark', 'Open a new window that runs the specified benchmarks.') + options.boolean('benchmark-test').describe('benchmark--test', 'Run a faster version of the benchmarks in headless mode.') options.alias('t', 'test').boolean('t').describe('t', 'Run the specified specs and exit with error code on failures.') options.alias('m', 'main-process').boolean('m').describe('m', 'Run the specified specs in the main process.') options.string('timeout').describe( @@ -78,6 +80,8 @@ module.exports = function parseCommandLine (processArgs) { const addToLastWindow = args['add'] const safeMode = args['safe'] const pathsToOpen = args._ + const benchmark = args['benchmark'] + const benchmarkTest = args['benchmark-test'] const test = args['test'] const mainProcess = args['main-process'] const timeout = args['timeout'] @@ -132,10 +136,29 @@ module.exports = function parseCommandLine (processArgs) { devResourcePath = normalizeDriveLetterName(devResourcePath) return { - resourcePath, devResourcePath, pathsToOpen, urlsToOpen, executedFrom, test, - version, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, socketPath, - userDataDir, profileStartup, timeout, setPortable, clearWindowState, - addToLastWindow, mainProcess, env: process.env + resourcePath, + devResourcePath, + pathsToOpen, + urlsToOpen, + executedFrom, + test, + version, + pidToKillWhenClosed, + devMode, + safeMode, + newWindow, + logFile, + socketPath, + userDataDir, + profileStartup, + timeout, + setPortable, + clearWindowState, + addToLastWindow, + mainProcess, + benchmark, + benchmarkTest, + env: process.env } } diff --git a/src/main-process/start.js b/src/main-process/start.js new file mode 100644 index 000000000..84ae9b8c2 --- /dev/null +++ b/src/main-process/start.js @@ -0,0 +1,115 @@ +const {app} = require('electron') +const fs = require('fs-plus') +const nslog = require('nslog') +const path = require('path') +const temp = require('temp') +const parseCommandLine = require('./parse-command-line') +const startCrashReporter = require('../crash-reporter-start') + +module.exports = function start (resourcePath, startTime) { + global.shellStartTime = startTime + + process.on('uncaughtException', function (error = {}) { + if (error.message != null) { + console.log(error.message) + } + + if (error.stack != null) { + console.log(error.stack) + } + }) + + const previousConsoleLog = console.log + console.log = nslog + + const args = parseCommandLine(process.argv.slice(1)) + setupAtomHome(args) + setupCompileCache() + + if (handleStartupEventWithSquirrel()) { + return + } else if (args.test && args.mainProcess) { + app.setPath('userData', temp.mkdirSync('atom-user-data-dir-for-main-process-tests')) + console.log = previousConsoleLog + app.on('ready', function () { + const testRunner = require(path.join(args.resourcePath, 'spec/main-process/mocha-test-runner')) + testRunner(args.pathsToOpen) + }) + return + } + + // NB: This prevents Win10 from showing dupe items in the taskbar + app.setAppUserModelId('com.squirrel.atom.atom') + + function addPathToOpen (event, pathToOpen) { + event.preventDefault() + args.pathsToOpen.push(pathToOpen) + } + + function addUrlToOpen (event, urlToOpen) { + event.preventDefault() + args.urlsToOpen.push(urlToOpen) + } + + app.on('open-file', addPathToOpen) + app.on('open-url', addUrlToOpen) + app.on('will-finish-launching', startCrashReporter) + + if (args.userDataDir != null) { + app.setPath('userData', args.userDataDir) + } else if (args.test || args.benchmark || args.benchmarkTest) { + app.setPath('userData', temp.mkdirSync('atom-test-data')) + } + + app.on('ready', function () { + app.removeListener('open-file', addPathToOpen) + app.removeListener('open-url', addUrlToOpen) + const AtomApplication = require(path.join(args.resourcePath, 'src', 'main-process', 'atom-application')) + AtomApplication.open(args) + }) +} + +function handleStartupEventWithSquirrel () { + if (process.platform !== 'win32') { + return false + } + + const SquirrelUpdate = require('./squirrel-update') + const squirrelCommand = process.argv[1] + return SquirrelUpdate.handleStartupEvent(app, squirrelCommand) +} + +function setupAtomHome ({setPortable}) { + if (process.env.ATOM_HOME) { + return + } + + let atomHome = path.join(app.getPath('home'), '.atom') + const AtomPortable = require('./atom-portable') + + if (setPortable && !AtomPortable.isPortableInstall(process.platform, process.env.ATOM_HOME, atomHome)) { + try { + AtomPortable.setPortable(atomHome) + } catch (error) { + console.log(`Failed copying portable directory '${atomHome}' to '${AtomPortable.getPortableAtomHomePath()}'`) + console.log(`${error.message} ${error.stack}`) + } + } + + if (AtomPortable.isPortableInstall(process.platform, process.env.ATOM_HOME, atomHome)) { + atomHome = AtomPortable.getPortableAtomHomePath() + } + + try { + atomHome = fs.realpathSync(atomHome) + } catch (e) { + // Don't throw an error if atomHome doesn't exist. + } + + process.env.ATOM_HOME = atomHome +} + +function setupCompileCache () { + const CompileCache = require('../compile-cache') + CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME) +} diff --git a/src/native-compile-cache.js b/src/native-compile-cache.js index 50fa71fc3..a9857fc0c 100644 --- a/src/native-compile-cache.js +++ b/src/native-compile-cache.js @@ -45,7 +45,7 @@ class NativeCompileCache { Module.prototype._compile = function (content, filename) { let moduleSelf = this // remove shebang - content = content.replace(/^\#\!.*/, '') + content = content.replace(/^#!.*/, '') function require (path) { return moduleSelf.require(path) } diff --git a/src/notification-manager.coffee b/src/notification-manager.coffee index 1cb144bdc..4beab82b9 100644 --- a/src/notification-manager.coffee +++ b/src/notification-manager.coffee @@ -1,4 +1,4 @@ -{Emitter, Disposable} = require 'event-kit' +{Emitter} = require 'event-kit' Notification = require '../src/notification' # Public: A notification manager used to create {Notification}s to be shown diff --git a/src/null-grammar.js b/src/null-grammar.js index 0ca3f83f1..fe9c3889e 100644 --- a/src/null-grammar.js +++ b/src/null-grammar.js @@ -2,12 +2,39 @@ import {Disposable} from 'event-kit' -export default Object.freeze({ +export default { name: 'Null Grammar', - scopeName: 'text.plain', + scopeName: 'text.plain.null-grammar', + scopeForId (id) { + if (id === -1 || id === -2) { + return this.scopeName + } else { + return null + } + }, + startIdForScope (scopeName) { + if (scopeName === this.scopeName) { + return -1 + } else { + return null + } + }, + endIdForScope (scopeName) { + if (scopeName === this.scopeName) { + return -2 + } else { + return null + } + }, + tokenizeLine (text) { + return { + tags: [this.startIdForScope(this.scopeName), text.length, this.endIdForScope(this.scopeName)], + ruleStack: null + } + }, onDidUpdate (callback) { return new Disposable(noop) } -}) +} function noop () {} diff --git a/src/package-manager.coffee b/src/package-manager.coffee index 8f2924358..84a36dd78 100644 --- a/src/package-manager.coffee +++ b/src/package-manager.coffee @@ -45,6 +45,8 @@ class PackageManager @packageDirPaths.push(path.join(configDirPath, "packages")) @packagesCache = require('../package.json')?._atomPackages ? {} + @initialPackagesLoaded = false + @initialPackagesActivated = false @loadedPackages = {} @activePackages = {} @activatingPackages = {} @@ -241,6 +243,9 @@ class PackageManager isPackageActive: (name) -> @getActivePackage(name)? + # Public: Returns a {Boolean} indicating whether package activation has occurred. + hasActivatedInitialPackages: -> @initialPackagesActivated + ### Section: Accessing loaded packages ### @@ -271,6 +276,9 @@ class PackageManager isPackageLoaded: (name) -> @getLoadedPackage(name)? + # Public: Returns a {Boolean} indicating whether package loading has occurred. + hasLoadedInitialPackages: -> @initialPackagesLoaded + ### Section: Accessing available packages ### @@ -284,7 +292,7 @@ class PackageManager packagePaths.push(packagePath) if fs.isDirectorySync(packagePath) packagesPath = path.join(@resourcePath, 'node_modules') - for packageName, packageVersion of @getPackageDependencies() + for packageName of @getPackageDependencies() packagePath = path.join(packagesPath, packageName) packagePaths.push(packagePath) if fs.isDirectorySync(packagePath) @@ -364,6 +372,7 @@ class PackageManager @config.transact => @loadPackage(packagePath) for packagePath in packagePaths return + @initialPackagesLoaded = true @emitter.emit 'did-load-initial-packages' loadPackage: (nameOrPath) -> @@ -426,6 +435,7 @@ class PackageManager promises = promises.concat(activator.activatePackages(packages)) Promise.all(promises).then => @triggerDeferredActivationHooks() + @initialPackagesActivated = true @emitter.emit 'did-activate-initial-packages' # another type of package manager can handle other package types. @@ -486,15 +496,15 @@ class PackageManager # Deactivate all packages deactivatePackages: -> @config.transact => - @deactivatePackage(pack.name) for pack in @getLoadedPackages() + @deactivatePackage(pack.name, true) for pack in @getLoadedPackages() return @unobserveDisabledPackages() @unobservePackagesWithKeymapsDisabled() # Deactivate the package with the given name - deactivatePackage: (name) -> + deactivatePackage: (name, suppressSerialization) -> pack = @getLoadedPackage(name) - @serializePackage(pack) if @isPackageActive(pack.name) + @serializePackage(pack) if not suppressSerialization and @isPackageActive(pack.name) pack.deactivate() delete @activePackages[pack.name] delete @activatingPackages[pack.name] diff --git a/src/package.coffee b/src/package.coffee index bb8707ede..323dfa8d2 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -85,6 +85,7 @@ class Package @loadMenus() @loadStylesheets() @registerDeserializerMethods() + @activateCoreStartupServices() @configSchemaRegisteredOnLoad = @registerConfigSchemaFromMetadata() @settingsPromise = @loadSettings() if @shouldRequireMainModuleOnLoad() and not @mainModule? @@ -250,7 +251,7 @@ class Package if @bundledPackage and @packageManager.packagesCache[@name]? @keymaps = (["#{@packageManager.resourcePath}#{path.sep}#{keymapPath}", keymapObject] for keymapPath, keymapObject of @packageManager.packagesCache[@name].keymaps) else - @keymaps = @getKeymapPaths().map (keymapPath) -> [keymapPath, CSON.readFileSync(keymapPath) ? {}] + @keymaps = @getKeymapPaths().map (keymapPath) -> [keymapPath, CSON.readFileSync(keymapPath, allowDuplicateKeys: false) ? {}] return loadMenus: -> @@ -290,6 +291,15 @@ class Package @mainModule[methodName](state, atomEnvironment) return + activateCoreStartupServices: -> + if directoryProviderService = @metadata.providedServices?['atom.directory-provider'] + @requireMainModule() + servicesByVersion = {} + for version, methodName of directoryProviderService.versions + if typeof @mainModule[methodName] is 'function' + servicesByVersion[version] = @mainModule[methodName]() + @packageManager.serviceHub.provide('atom.directory-provider', servicesByVersion) + registerViewProviders: -> if @metadata.viewProviders? and not @registeredViewProviders @requireMainModule() @@ -412,8 +422,6 @@ class Package @settingsActivated = false reloadStylesheets: -> - oldSheets = _.clone(@stylesheets) - try @loadStylesheets() catch error diff --git a/src/pane-container.coffee b/src/pane-container.coffee index 522c7c2cf..fc092122e 100644 --- a/src/pane-container.coffee +++ b/src/pane-container.coffee @@ -1,6 +1,5 @@ -{find, flatten} = require 'underscore-plus' +{find} = require 'underscore-plus' {Emitter, CompositeDisposable} = require 'event-kit' -Gutter = require './gutter' Model = require './model' Pane = require './pane' ItemRegistry = require './item-registry' diff --git a/src/pane.coffee b/src/pane.coffee index f7487a6e1..e4003ad35 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -21,7 +21,7 @@ class Pane extends Model focused: false @deserialize: (state, {deserializers, applicationDelegate, config, notifications}) -> - {items, itemStackIndices, activeItemIndex, activeItemURI, activeItemUri} = state + {items, activeItemIndex, activeItemURI, activeItemUri} = state activeItemURI ?= activeItemUri items = items.map (itemState) -> deserializers.deserialize(itemState) state.activeItem = items[activeItemIndex] diff --git a/src/project.coffee b/src/project.coffee index 2f3d58efb..52a3223e0 100644 --- a/src/project.coffee +++ b/src/project.coffee @@ -1,5 +1,4 @@ path = require 'path' -url = require 'url' _ = require 'underscore-plus' fs = require 'fs-plus' @@ -8,8 +7,6 @@ TextBuffer = require 'text-buffer' DefaultDirectoryProvider = require './default-directory-provider' Model = require './model' -TextEditor = require './text-editor' -Task = require './task' GitRepositoryProvider = require './git-repository-provider' # Extended: Represents a project that's opened in Atom. @@ -56,7 +53,6 @@ class Project extends Model deserialize: (state) -> state.paths = [state.path] if state.path? # backward compatibility - state.paths = state.paths.filter (directoryPath) -> fs.isDirectorySync(directoryPath) @buffers = _.compact state.buffers.map (bufferState) -> # Check that buffer's file path is accessible @@ -181,10 +177,9 @@ class Project extends Model break if directory = provider.directoryForURISync?(projectPath) directory ?= @defaultDirectoryProvider.directoryForURISync(projectPath) - directoryExists = directory.existsSync() - for rootDirectory in @getDirectories() - return if rootDirectory.getPath() is directory.getPath() - return if not directoryExists and rootDirectory.contains(directory.getPath()) + return unless directory.existsSync() + for existingDirectory in @getDirectories() + return if existingDirectory.getPath() is directory.getPath() @rootDirectories.push(directory) diff --git a/src/register-default-commands.coffee b/src/register-default-commands.coffee index 2b05f6f0f..8196d9237 100644 --- a/src/register-default-commands.coffee +++ b/src/register-default-commands.coffee @@ -1,4 +1,5 @@ {ipcRenderer} = require 'electron' +Grim = require 'grim' module.exports = ({commandRegistry, commandInstaller, config, notificationManager, project, clipboard}) -> commandRegistry.add 'atom-workspace', @@ -53,6 +54,7 @@ module.exports = ({commandRegistry, commandInstaller, config, notificationManage 'application:open-your-stylesheet': -> ipcRenderer.send('command', 'application:open-your-stylesheet') 'application:open-license': -> @getModel().openLicense() 'window:run-package-specs': -> @runPackageSpecs() + 'window:run-benchmarks': -> @runBenchmarks() 'window:focus-next-pane': -> @getModel().activateNextPane() 'window:focus-previous-pane': -> @getModel().activatePreviousPane() 'window:focus-pane-above': -> @focusPaneViewAbove() diff --git a/src/scan-handler.coffee b/src/scan-handler.coffee index 18e00dedc..8ee8f715e 100644 --- a/src/scan-handler.coffee +++ b/src/scan-handler.coffee @@ -5,8 +5,6 @@ async = require "async" module.exports = (rootPaths, regexSource, options) -> callback = @async() - rootPath = rootPaths[0] - PATHS_COUNTER_SEARCHED_CHUNK = 50 pathsSearched = 0 diff --git a/src/scoped-properties.coffee b/src/scoped-properties.coffee index c7257083e..f8f8b4311 100644 --- a/src/scoped-properties.coffee +++ b/src/scoped-properties.coffee @@ -1,5 +1,4 @@ CSON = require 'season' -{CompositeDisposable} = require 'event-kit' module.exports = class ScopedProperties diff --git a/src/selection.coffee b/src/selection.coffee index 8e9bf827e..5eaa9c8dd 100644 --- a/src/selection.coffee +++ b/src/selection.coffee @@ -14,7 +14,7 @@ class Selection extends Model initialScreenRange: null wordwise: false - constructor: ({@cursor, @marker, @editor, id, @clipboard}) -> + constructor: ({@cursor, @marker, @editor, id}) -> @emitter = new Emitter @assignId(id) @@ -512,7 +512,7 @@ class Selection extends Model joinMarker = @editor.markBufferRange(selectedRange, invalidate: 'never') rowCount = Math.max(1, selectedRange.getRowCount() - 1) - for row in [0...rowCount] + for [0...rowCount] @cursor.setBufferPosition([selectedRange.start.row]) @cursor.moveToEndOfLine() @@ -605,7 +605,7 @@ class Selection extends Model startLevel = @editor.indentLevelForLine(precedingText) if maintainClipboard - {text: clipboardText, metadata} = @clipboard.readWithMetadata() + {text: clipboardText, metadata} = @editor.constructor.clipboard.readWithMetadata() metadata ?= {} unless metadata.selections? metadata.selections = [{ @@ -618,9 +618,9 @@ class Selection extends Model indentBasis: startLevel, fullLine: fullLine }) - @clipboard.write([clipboardText, selectionText].join("\n"), metadata) + @editor.constructor.clipboard.write([clipboardText, selectionText].join("\n"), metadata) else - @clipboard.write(selectionText, { + @editor.constructor.clipboard.write(selectionText, { indentBasis: startLevel, fullLine: fullLine }) @@ -656,7 +656,7 @@ class Selection extends Model # * `autoIndent` If `true`, the line is indented to an automatically-inferred # level. Otherwise, {TextEditor::getTabText} is inserted. indent: ({autoIndent}={}) -> - {row, column} = @cursor.getBufferPosition() + {row} = @cursor.getBufferPosition() if @isEmpty() @cursor.skipLeadingWhitespace() diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index ca1017617..d2647d767 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -1,4 +1,3 @@ -_ = require 'underscore-plus' scrollbarStyle = require 'scrollbar-style' {Range, Point} = require 'text-buffer' {CompositeDisposable} = require 'event-kit' @@ -195,6 +194,10 @@ class TextEditorComponent becameVisible: -> @updatesPaused = true + # Always invalidate LinesYardstick measurements when the editor becomes + # visible again, because content might have been reflowed and measurements + # could be outdated. + @invalidateMeasurements() @measureScrollbars() if @measureScrollbarsWhenShown @sampleFontStyling() @sampleBackgroundColors() @@ -339,8 +342,6 @@ class TextEditorComponent @scopedConfigDisposables = new CompositeDisposable @disposables.add(@scopedConfigDisposables) - scope = @editor.getRootScopeDescriptor() - focused: -> if @mounted @presenter.setFocused(true) diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index 5d8254f7c..27a29bed6 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -1,8 +1,5 @@ {Emitter, CompositeDisposable} = require 'event-kit' -Path = require 'path' -{defaults} = require 'underscore-plus' TextBuffer = require 'text-buffer' -TextEditor = require './text-editor' TextEditorComponent = require './text-editor-component' StylesElement = require './styles-element' diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 2c8ec44d5..bbefc91c0 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1,4 +1,4 @@ -{CompositeDisposable, Disposable, Emitter} = require 'event-kit' +{CompositeDisposable, Emitter} = require 'event-kit' {Point, Range} = require 'text-buffer' _ = require 'underscore-plus' Decoration = require './decoration' @@ -9,7 +9,7 @@ class TextEditorPresenter startBlinkingCursorsAfterDelay: null stoppedScrollingTimeoutId: null mouseWheelScreenRow: null - overlayDimensions: {} + overlayDimensions: null minimumReflowInterval: 200 constructor: (params) -> @@ -31,6 +31,7 @@ class TextEditorPresenter @lineDecorationsByScreenRow = {} @lineNumberDecorationsByScreenRow = {} @customGutterDecorationsByGutterName = {} + @overlayDimensions = {} @observedBlockDecorations = new Set() @invalidatedDimensionsByBlockDecoration = new Set() @invalidateAllBlockDecorationsDimensions = false @@ -137,6 +138,11 @@ class TextEditorPresenter @shouldUpdateDecorations = true observeModel: -> + @disposables.add @model.displayLayer.onDidReset => + @spliceBlockDecorationsInRange(0, Infinity, Infinity) + @shouldUpdateDecorations = true + @emitDidUpdateState() + @disposables.add @model.displayLayer.onDidChangeSync (changes) => for change in changes startRow = change.start.row @@ -291,24 +297,21 @@ class TextEditorPresenter tileForRow: (row) -> row - (row % @tileSize) - constrainRow: (row) -> - Math.max(0, Math.min(row, @model.getScreenLineCount())) - getStartTileRow: -> - @constrainRow(@tileForRow(@startRow ? 0)) + @tileForRow(@startRow ? 0) getEndTileRow: -> - @constrainRow(@tileForRow(@endRow ? 0)) + @tileForRow(@endRow ? 0) isValidScreenRow: (screenRow) -> - screenRow >= 0 and screenRow < @model.getScreenLineCount() + screenRow >= 0 and screenRow < @model.getApproximateScreenLineCount() getScreenRowsToRender: -> startRow = @getStartTileRow() - endRow = @constrainRow(@getEndTileRow() + @tileSize) + endRow = @getEndTileRow() + @tileSize screenRows = [startRow...endRow] - longestScreenRow = @model.getLongestScreenRow() + longestScreenRow = @model.getApproximateLongestScreenRow() if longestScreenRow? screenRows.push(longestScreenRow) if @screenRowsToMeasure? @@ -354,7 +357,7 @@ class TextEditorPresenter zIndex = 0 for tileStartRow in [@tileForRow(endRow)..@tileForRow(startRow)] by -@tileSize - tileEndRow = @constrainRow(tileStartRow + @tileSize) + tileEndRow = tileStartRow + @tileSize rowsWithinTile = [] while screenRowIndex >= 0 @@ -389,7 +392,7 @@ class TextEditorPresenter visibleTiles[tileStartRow] = true zIndex++ - if @mouseWheelScreenRow? and 0 <= @mouseWheelScreenRow < @model.getScreenLineCount() + if @mouseWheelScreenRow? and 0 <= @mouseWheelScreenRow < @model.getApproximateScreenLineCount() mouseWheelTile = @tileForRow(@mouseWheelScreenRow) unless visibleTiles[mouseWheelTile]? @@ -408,8 +411,7 @@ class TextEditorPresenter visibleLineIds = {} for screenRow in screenRows line = @linesByScreenRow.get(screenRow) - unless line? - throw new Error("No line exists for row #{screenRow}. Last screen row: #{@model.getLastScreenRow()}") + continue unless line? visibleLineIds[line.id] = true precedingBlockDecorations = @precedingBlockDecorationsByScreenRow[screenRow] ? [] @@ -597,7 +599,9 @@ class TextEditorPresenter visibleLineNumberIds = {} for screenRow in screenRows when @isRowRendered(screenRow) - lineId = @linesByScreenRow.get(screenRow).id + line = @linesByScreenRow.get(screenRow) + continue unless line? + lineId = line.id {bufferRow, softWrappedAtStart: softWrapped} = @displayLayer.softWrapDescriptorForScreenRow(screenRow) foldable = not softWrapped and @model.isFoldableAtBufferRow(bufferRow) decorationClasses = @lineNumberDecorationClassesForRow(screenRow) @@ -624,7 +628,7 @@ class TextEditorPresenter return unless @scrollTop? and @lineHeight? and @height? @endRow = Math.min( - @model.getScreenLineCount(), + @model.getApproximateScreenLineCount(), @lineTopIndex.rowForPixelPosition(@scrollTop + @height + @lineHeight - 1) + 1 ) @@ -658,7 +662,7 @@ class TextEditorPresenter updateVerticalDimensions: -> if @lineHeight? oldContentHeight = @contentHeight - @contentHeight = Math.round(@lineTopIndex.pixelPositionAfterBlocksForRow(@model.getScreenLineCount())) + @contentHeight = Math.round(@lineTopIndex.pixelPositionAfterBlocksForRow(@model.getApproximateScreenLineCount())) if @contentHeight isnt oldContentHeight @updateHeight() @@ -668,7 +672,7 @@ class TextEditorPresenter updateHorizontalDimensions: -> if @baseCharacterWidth? oldContentWidth = @contentWidth - rightmostPosition = @model.getRightmostScreenPosition() + rightmostPosition = @model.getApproximateRightmostScreenPosition() @contentWidth = @pixelPositionForScreenPosition(rightmostPosition).left @contentWidth += @scrollLeft @contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width @@ -886,13 +890,11 @@ class TextEditorPresenter setHorizontalScrollbarHeight: (horizontalScrollbarHeight) -> unless @measuredHorizontalScrollbarHeight is horizontalScrollbarHeight - oldHorizontalScrollbarHeight = @measuredHorizontalScrollbarHeight @measuredHorizontalScrollbarHeight = horizontalScrollbarHeight @emitDidUpdateState() setVerticalScrollbarWidth: (verticalScrollbarWidth) -> unless @measuredVerticalScrollbarWidth is verticalScrollbarWidth - oldVerticalScrollbarWidth = @measuredVerticalScrollbarWidth @measuredVerticalScrollbarWidth = verticalScrollbarWidth @emitDidUpdateState() @@ -922,7 +924,6 @@ class TextEditorPresenter setContentFrameWidth: (contentFrameWidth) -> if @contentFrameWidth isnt contentFrameWidth or @editorWidthInChars? - oldContentFrameWidth = @contentFrameWidth @contentFrameWidth = contentFrameWidth @editorWidthInChars = null @updateScrollbarDimensions() @@ -1122,7 +1123,7 @@ class TextEditorPresenter @updateHighlightState(decorationId, properties, screenRange) for tileId, tileState of @state.content.tiles - for id, highlight of tileState.highlights + for id of tileState.highlights delete tileState.highlights[id] unless @visibleHighlights[tileId]?[id]? return @@ -1532,7 +1533,7 @@ class TextEditorPresenter [@startRow, @endRow] isRowRendered: (row) -> - @getStartTileRow() <= row < @constrainRow(@getEndTileRow() + @tileSize) + @getStartTileRow() <= row < @getEndTileRow() + @tileSize isOpenTagCode: (tagCode) -> @displayLayer.isOpenTagCode(tagCode) diff --git a/src/text-editor-registry.js b/src/text-editor-registry.js index bda3712e7..b29a3887c 100644 --- a/src/text-editor-registry.js +++ b/src/text-editor-registry.js @@ -39,9 +39,8 @@ const GRAMMAR_SELECTION_RANGE = Range(Point.ZERO, Point(10, 0)).freeze() // done using your editor, be sure to call `dispose` on the returned disposable // to avoid leaking editors. export default class TextEditorRegistry { - constructor ({config, grammarRegistry, clipboard, assert, packageManager}) { + constructor ({config, grammarRegistry, assert, packageManager}) { this.assert = assert - this.clipboard = clipboard this.config = config this.grammarRegistry = grammarRegistry this.scopedSettingsDelegate = new ScopedSettingsDelegate(config) @@ -109,10 +108,7 @@ export default class TextEditorRegistry { } build (params) { - params = Object.assign({ - clipboard: this.clipboard, - assert: this.assert - }, params) + params = Object.assign({assert: this.assert}, params) let scope = null if (params.buffer) { @@ -157,7 +153,7 @@ export default class TextEditorRegistry { // configuration. maintainConfig (editor) { if (this.editorsWithMaintainedConfig.has(editor)) { - return + return new Disposable(noop) } this.editorsWithMaintainedConfig.add(editor) @@ -202,7 +198,7 @@ export default class TextEditorRegistry { // grammar. maintainGrammar (editor) { if (this.editorsWithMaintainedGrammar.has(editor)) { - return + return new Disposable(noop) } this.editorsWithMaintainedGrammar.add(editor) @@ -391,6 +387,8 @@ function shouldEditorUseSoftTabs (editor, tabType, softTabs) { } } +function noop () {} + class ScopedSettingsDelegate { constructor (config) { this.config = config diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 69eabbbd8..6907db8fe 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -1,7 +1,8 @@ _ = require 'underscore-plus' path = require 'path' +fs = require 'fs-plus' Grim = require 'grim' -{CompositeDisposable, Emitter} = require 'event-kit' +{CompositeDisposable, Disposable, Emitter} = require 'event-kit' {Point, Range} = TextBuffer = require 'text-buffer' LanguageMode = require './language-mode' DecorationManager = require './decoration-manager' @@ -58,6 +59,9 @@ ZERO_WIDTH_NBSP = '\ufeff' # soft wraps and folds to ensure your code interacts with them correctly. module.exports = class TextEditor extends Model + @setClipboard: (clipboard) -> + @clipboard = clipboard + serializationVersion: 1 buffer: null @@ -113,7 +117,6 @@ class TextEditor extends Model if state.displayLayer = state.buffer.getDisplayLayer(state.displayLayerId) state.selectionsMarkerLayer = state.displayLayer.getMarkerLayer(state.selectionsMarkerLayerId) - state.clipboard = atomEnvironment.clipboard state.assert = atomEnvironment.assert.bind(atomEnvironment) editor = new this(state) if state.registered @@ -122,19 +125,20 @@ class TextEditor extends Model editor constructor: (params={}) -> + unless @constructor.clipboard? + throw new Error("Must call TextEditor.setClipboard at least once before creating TextEditor instances") + super { @softTabs, @firstVisibleScreenRow, @firstVisibleScreenColumn, initialLine, initialColumn, tabLength, @softWrapped, @decorationManager, @selectionsMarkerLayer, @buffer, suppressCursorCreation, - @mini, @placeholderText, lineNumberGutterVisible, @largeFileMode, @clipboard, + @mini, @placeholderText, lineNumberGutterVisible, @largeFileMode, @assert, grammar, @showInvisibles, @autoHeight, @autoWidth, @scrollPastEnd, @editorWidthInChars, @tokenizedBuffer, @displayLayer, @invisibles, @showIndentGuide, @softWrapped, @softWrapAtPreferredLineLength, @preferredLineLength } = params - throw new Error("Must pass a clipboard parameter when constructing TextEditors") unless @clipboard? - @assert ?= (condition) -> condition @firstVisibleScreenRow ?= 0 @firstVisibleScreenColumn ?= 0 @@ -146,7 +150,7 @@ class TextEditor extends Model @hasTerminatedPendingState = false @mini ?= false - @scrollPastEnd ?= true + @scrollPastEnd ?= false @showInvisibles ?= true @softTabs ?= true tabLength ?= 2 @@ -180,6 +184,10 @@ class TextEditor extends Model else @displayLayer = @buffer.addDisplayLayer(displayLayerParams) + @backgroundWorkHandle = requestIdleCallback(@doBackgroundWork) + @disposables.add new Disposable => + cancelIdleCallback(@backgroundWorkHandle) if @backgroundWorkHandle? + @displayLayer.setTextDecorationLayer(@tokenizedBuffer) @defaultMarkerLayer = @displayLayer.addMarkerLayer() @selectionsMarkerLayer ?= @addMarkerLayer(maintainHistory: true, persistent: true) @@ -206,6 +214,13 @@ class TextEditor extends Model priority: 0 visible: lineNumberGutterVisible + doBackgroundWork: (deadline) => + if @displayLayer.doBackgroundWork(deadline) + @presenter?.updateVerticalDimensions() + @backgroundWorkHandle = requestIdleCallback(@doBackgroundWork) + else + @backgroundWorkHandle = null + update: (params) -> displayLayerParams = {} @@ -240,7 +255,7 @@ class TextEditor extends Model displayLayerParams.atomicSoftTabs = value when 'tabLength' - if value isnt @tokenizedBuffer.getTabLength() + if value? and value isnt @tokenizedBuffer.getTabLength() @tokenizedBuffer.setTabLength(value) displayLayerParams.tabLength = value @@ -390,6 +405,9 @@ class TextEditor extends Model @disposables.add @displayLayer.onDidChangeSync (e) => @mergeIntersectingSelections() @emitter.emit 'did-change', e + @disposables.add @displayLayer.onDidReset => + @mergeIntersectingSelections() + @emitter.emit 'did-change', {} destroyed: -> @disposables.dispose() @@ -704,7 +722,7 @@ class TextEditor extends Model suppressCursorCreation: true, tabLength: @tokenizedBuffer.getTabLength(), @firstVisibleScreenRow, @firstVisibleScreenColumn, - @clipboard, @assert, displayLayer, grammar: @getGrammar(), + @assert, displayLayer, grammar: @getGrammar(), @autoWidth, @autoHeight }) @@ -803,12 +821,13 @@ class TextEditor extends Model allPathSegments = [] for textEditor in atom.workspace.getTextEditors() when textEditor isnt this if textEditor.getFileName() is fileName - allPathSegments.push(textEditor.getDirectoryPath().split(path.sep)) + directoryPath = fs.tildify(textEditor.getDirectoryPath()) + allPathSegments.push(directoryPath.split(path.sep)) if allPathSegments.length is 0 return fileName - ourPathSegments = @getDirectoryPath().split(path.sep) + ourPathSegments = fs.tildify(@getDirectoryPath()).split(path.sep) allPathSegments.push ourPathSegments loop @@ -905,6 +924,8 @@ class TextEditor extends Model # editor. This accounts for folds. getScreenLineCount: -> @displayLayer.getScreenLineCount() + getApproximateScreenLineCount: -> @displayLayer.getApproximateScreenLineCount() + # Essential: Returns a {Number} representing the last zero-indexed buffer row # number of the editor. getLastBufferRow: -> @buffer.getLastRow() @@ -951,7 +972,6 @@ class TextEditor extends Model tokens screenLineForScreenRow: (screenRow) -> - return if screenRow < 0 or screenRow > @getLastScreenRow() @displayLayer.getScreenLines(screenRow, screenRow + 1)[0] bufferRowForScreenRow: (screenRow) -> @@ -969,10 +989,14 @@ class TextEditor extends Model getRightmostScreenPosition: -> @displayLayer.getRightmostScreenPosition() + getApproximateRightmostScreenPosition: -> @displayLayer.getApproximateRightmostScreenPosition() + getMaxScreenLineLength: -> @getRightmostScreenPosition().column getLongestScreenRow: -> @getRightmostScreenPosition().row + getApproximateLongestScreenRow: -> @getApproximateRightmostScreenPosition().row + lineLengthForScreenRow: (screenRow) -> @displayLayer.lineLengthForScreenRow(screenRow) # Returns the range for the given buffer row. @@ -2716,7 +2740,7 @@ class TextEditor extends Model # Returns the new {Selection}. addSelection: (marker, options={}) -> cursor = @addCursor(marker) - selection = new Selection(Object.assign({editor: this, marker, cursor, @clipboard}, options)) + selection = new Selection(Object.assign({editor: this, marker, cursor}, options)) @selections.push(selection) selectionBufferRange = selection.getBufferRange() @mergeIntersectingSelections(preserveFolds: options.preserveFolds) @@ -2822,7 +2846,7 @@ class TextEditor extends Model # Essential: Enable or disable soft tabs for this editor. # # * `softTabs` A {Boolean} - setSoftTabs: (@softTabs) -> @update({softTabs}) + setSoftTabs: (@softTabs) -> @update({@softTabs}) # Returns a {Boolean} indicating whether atomic soft tabs are enabled for this editor. hasAtomicSoftTabs: -> @displayLayer.atomicSoftTabs @@ -2863,7 +2887,7 @@ class TextEditor extends Model # whitespace. usesSoftTabs: -> for bufferRow in [0..@buffer.getLastRow()] - continue if @tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment() + continue if @tokenizedBuffer.tokenizedLines[bufferRow]?.isComment() line = @buffer.lineForRow(bufferRow) return true if line[0] is ' ' @@ -2928,9 +2952,9 @@ class TextEditor extends Model Section: Indentation ### - # Essential: Get the indentation level of the given a buffer row. + # Essential: Get the indentation level of the given buffer row. # - # Returns how deeply the given row is indented based on the soft tabs and + # Determines how deeply the given row is indented based on the soft tabs and # tab length settings of this editor. Note that if soft tabs are enabled and # the tab length is 2, a row with 4 leading spaces would have an indentation # level of 2. @@ -2971,7 +2995,7 @@ class TextEditor extends Model # Extended: Get the indentation level of the given line of text. # - # Returns how deeply the given line is indented based on the soft tabs and + # Determines how deeply the given line is indented based on the soft tabs and # tab length settings of this editor. Note that if soft tabs are enabled and # the tab length is 2, a row with 4 leading spaces would have an indentation # level of 2. @@ -3125,7 +3149,7 @@ class TextEditor extends Model # # * `options` (optional) See {Selection::insertText}. pasteText: (options={}) -> - {text: clipboardText, metadata} = @clipboard.readWithMetadata() + {text: clipboardText, metadata} = @constructor.clipboard.readWithMetadata() return false unless @emitWillInsertTextEvent(clipboardText) metadata ?= {} diff --git a/src/theme-manager.coffee b/src/theme-manager.coffee index fbc722365..32fabf724 100644 --- a/src/theme-manager.coffee +++ b/src/theme-manager.coffee @@ -1,6 +1,6 @@ path = require 'path' _ = require 'underscore-plus' -{Emitter, Disposable, CompositeDisposable} = require 'event-kit' +{Emitter, CompositeDisposable} = require 'event-kit' {File} = require 'pathwatcher' fs = require 'fs-plus' diff --git a/src/token.coffee b/src/token.coffee index d531ba04a..902f54fa2 100644 --- a/src/token.coffee +++ b/src/token.coffee @@ -1,7 +1,6 @@ _ = require 'underscore-plus' StartDotRegex = /^\.?/ -WhitespaceRegex = /\S/ # Represents a single unit of text as selected by a grammar. module.exports = diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee index e04274c66..ce56e0388 100644 --- a/src/tokenized-buffer.coffee +++ b/src/tokenized-buffer.coffee @@ -1,11 +1,9 @@ _ = require 'underscore-plus' {CompositeDisposable, Emitter} = require 'event-kit' {Point, Range} = require 'text-buffer' -{ScopeSelector} = require 'first-mate' Model = require './model' TokenizedLine = require './tokenized-line' TokenIterator = require './token-iterator' -Token = require './token' ScopeDescriptor = require './scope-descriptor' TokenizedBufferIterator = require './tokenized-buffer-iterator' NullGrammar = require './null-grammar' @@ -38,7 +36,6 @@ class TokenizedBuffer extends Model @tokenIterator = new TokenIterator(this) @disposables.add @buffer.registerTextDecorationLayer(this) - @rootScopeDescriptor = new ScopeDescriptor(scopes: ['text.plain']) @setGrammar(grammar ? NullGrammar) @@ -49,10 +46,7 @@ class TokenizedBuffer extends Model new TokenizedBufferIterator(this) getInvalidatedRanges: -> - if @invalidatedRange? - [@invalidatedRange] - else - [] + [] onDidInvalidateRange: (fn) -> @emitter.on 'did-invalidate-range', fn @@ -100,14 +94,17 @@ class TokenizedBuffer extends Model false retokenizeLines: -> - lastRow = @buffer.getLastRow() @fullyTokenized = false - @tokenizedLines = new Array(lastRow + 1) + @tokenizedLines = new Array(@buffer.getLineCount()) @invalidRows = [] - @invalidateRow(0) + if @largeFileMode or @grammar.name is 'Null Grammar' + @markTokenizationComplete() + else + @invalidateRow(0) setVisible: (@visible) -> - @tokenizeInBackground() if @visible + if @visible and @grammar.name isnt 'Null Grammar' and not @largeFileMode + @tokenizeInBackground() getTabLength: -> @tabLength @@ -122,12 +119,6 @@ class TokenizedBuffer extends Model @tokenizeNextChunk() if @isAlive() and @buffer.isAlive() tokenizeNextChunk: -> - # Short circuit null grammar which can just use the placeholder tokens - if (@grammar.name is 'Null Grammar') and @firstInvalidRow()? - @invalidRows = [] - @markTokenizationComplete() - return - rowsRemaining = @chunkSize while @firstInvalidRow()? and rowsRemaining > 0 @@ -172,8 +163,6 @@ class TokenizedBuffer extends Model return invalidateRow: (row) -> - return if @largeFileMode - @invalidRows.push(row) @invalidRows.sort (a, b) -> a - b @tokenizeInBackground() @@ -194,20 +183,19 @@ class TokenizedBuffer extends Model start = oldRange.start.row end = oldRange.end.row delta = newRange.end.row - oldRange.end.row + oldLineCount = oldRange.end.row - oldRange.start.row + 1 + newLineCount = newRange.end.row - newRange.start.row + 1 @updateInvalidRows(start, end, delta) previousEndStack = @stackForRow(end) # used in spill detection below - if @largeFileMode or @grammar is NullGrammar - newTokenizedLines = @buildPlaceholderTokenizedLinesForRows(start, end + delta) + if @largeFileMode or @grammar.name is 'Null Grammar' + _.spliceWithArray(@tokenizedLines, start, oldLineCount, new Array(newLineCount)) else newTokenizedLines = @buildTokenizedLinesForRows(start, end + delta, @stackForRow(start - 1), @openScopesForRow(start)) - _.spliceWithArray(@tokenizedLines, start, end - start + 1, newTokenizedLines) - - newEndStack = @stackForRow(end + delta) - if newEndStack and not _.isEqual(newEndStack, previousEndStack) - @invalidateRow(end + delta + 1) - - @invalidatedRange = Range(start, end) + _.spliceWithArray(@tokenizedLines, start, oldLineCount, newTokenizedLines) + newEndStack = @stackForRow(end + delta) + if newEndStack and not _.isEqual(newEndStack, previousEndStack) + @invalidateRow(end + delta + 1) isFoldableAtRow: (row) -> if @largeFileMode @@ -218,46 +206,39 @@ class TokenizedBuffer extends Model # Returns a {Boolean} indicating whether the given buffer row starts # a a foldable row range due to the code's indentation patterns. isFoldableCodeAtRow: (row) -> - # Investigating an exception that's occurring here due to the line being - # undefined. This should paper over the problem but we want to figure out - # what is happening: - tokenizedLine = @tokenizedLineForRow(row) - @assert tokenizedLine?, "TokenizedLine is undefined", (error) => - error.metadata = { - row: row - rowCount: @tokenizedLines.length - tokenizedBufferChangeCount: @changeCount - bufferChangeCount: @buffer.changeCount - } - - return false unless tokenizedLine? - - return false if @buffer.isRowBlank(row) or tokenizedLine.isComment() - nextRow = @buffer.nextNonBlankRow(row) - return false unless nextRow? - - @indentLevelForRow(nextRow) > @indentLevelForRow(row) + if 0 <= row <= @buffer.getLastRow() + nextRow = @buffer.nextNonBlankRow(row) + tokenizedLine = @tokenizedLines[row] + if @buffer.isRowBlank(row) or tokenizedLine?.isComment() or not nextRow? + false + else + @indentLevelForRow(nextRow) > @indentLevelForRow(row) + else + false isFoldableCommentAtRow: (row) -> previousRow = row - 1 nextRow = row + 1 - return false if nextRow > @buffer.getLastRow() - - (row is 0 or not @tokenizedLineForRow(previousRow).isComment()) and - @tokenizedLineForRow(row).isComment() and - @tokenizedLineForRow(nextRow).isComment() + if nextRow > @buffer.getLastRow() + false + else + Boolean( + not (@tokenizedLines[previousRow]?.isComment()) and + @tokenizedLines[row]?.isComment() and + @tokenizedLines[nextRow]?.isComment() + ) buildTokenizedLinesForRows: (startRow, endRow, startingStack, startingopenScopes) -> ruleStack = startingStack openScopes = startingopenScopes stopTokenizingAt = startRow + @chunkSize - tokenizedLines = for row in [startRow..endRow] + tokenizedLines = for row in [startRow..endRow] by 1 if (ruleStack or row is 0) and row < stopTokenizingAt tokenizedLine = @buildTokenizedLineForRow(row, ruleStack, openScopes) ruleStack = tokenizedLine.ruleStack openScopes = @scopesFromTags(openScopes, tokenizedLine.tags) else - tokenizedLine = @buildPlaceholderTokenizedLineForRow(row, openScopes) + tokenizedLine = undefined tokenizedLine if endRow >= stopTokenizingAt @@ -266,19 +247,6 @@ class TokenizedBuffer extends Model tokenizedLines - buildPlaceholderTokenizedLinesForRows: (startRow, endRow) -> - @buildPlaceholderTokenizedLineForRow(row) for row in [startRow..endRow] by 1 - - buildPlaceholderTokenizedLineForRow: (row) -> - if @grammar isnt NullGrammar - openScopes = [@grammar.startIdForScope(@grammar.scopeName)] - else - openScopes = [] - text = @buffer.lineForRow(row) - tags = [text.length] - lineEnding = @buffer.lineEndingForRow(row) - new TokenizedLine({openScopes, text, tags, lineEnding, @tokenIterator}) - buildTokenizedLineForRow: (row, ruleStack, openScopes) -> @buildTokenizedLineForRowWithText(row, @buffer.lineForRow(row), ruleStack, openScopes) @@ -288,8 +256,14 @@ class TokenizedBuffer extends Model new TokenizedLine({openScopes, text, tags, ruleStack, lineEnding, @tokenIterator}) tokenizedLineForRow: (bufferRow) -> - if 0 <= bufferRow < @tokenizedLines.length - @tokenizedLines[bufferRow] ?= @buildPlaceholderTokenizedLineForRow(bufferRow) + if 0 <= bufferRow <= @buffer.getLastRow() + if tokenizedLine = @tokenizedLines[bufferRow] + tokenizedLine + else + text = @buffer.lineForRow(bufferRow) + lineEnding = @buffer.lineEndingForRow(bufferRow) + tags = [@grammar.startIdForScope(@grammar.scopeName), text.length, @grammar.endIdForScope(@grammar.scopeName)] + @tokenizedLines[bufferRow] = new TokenizedLine({openScopes: [], text, tags, lineEnding, @tokenIterator}) tokenizedLinesForRows: (startRow, endRow) -> for row in [startRow..endRow] by 1 @@ -299,8 +273,7 @@ class TokenizedBuffer extends Model @tokenizedLines[bufferRow]?.ruleStack openScopesForRow: (bufferRow) -> - if bufferRow > 0 - precedingLine = @tokenizedLineForRow(bufferRow - 1) + if precedingLine = @tokenizedLines[bufferRow - 1] @scopesFromTags(precedingLine.openScopes, precedingLine.tags) else [] @@ -453,7 +426,7 @@ class TokenizedBuffer extends Model logLines: (start=0, end=@buffer.getLastRow()) -> for row in [start..end] - line = @tokenizedLineForRow(row).text + line = @tokenizedLines[row].text console.log row, line, line.length return diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index f8faad865..a65b3a793 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -1,5 +1,3 @@ -_ = require 'underscore-plus' -{isPairedCharacter, isCJKCharacter} = require './text-utils' Token = require './token' CommentScopeRegex = /(\b|\.)comment/ diff --git a/src/tooltip-manager.coffee b/src/tooltip-manager.coffee index 90f0ab8e6..4419ec740 100644 --- a/src/tooltip-manager.coffee +++ b/src/tooltip-manager.coffee @@ -2,7 +2,7 @@ _ = require 'underscore-plus' {Disposable, CompositeDisposable} = require 'event-kit' Tooltip = null -# Essential: Associates tooltips with HTML elements or selectors. +# Essential: Associates tooltips with HTML elements. # # You can get the `TooltipManager` via `atom.tooltips`. # @@ -46,25 +46,55 @@ Tooltip = null module.exports = class TooltipManager defaults: - delay: - show: 1000 - hide: 100 + trigger: 'hover' container: 'body' html: true placement: 'auto top' viewportPadding: 2 - constructor: ({@keymapManager}) -> + hoverDefaults: + {delay: {show: 1000, hide: 100}} + + constructor: ({@keymapManager, @viewRegistry}) -> # Essential: Add a tooltip to the given element. # # * `target` An `HTMLElement` - # * `options` See http://getbootstrap.com/javascript/#tooltips-options for a - # full list of options. You can also supply the following additional options: + # * `options` An object with one or more of the following options: # * `title` A {String} or {Function} to use for the text in the tip. If - # given a function, `this` will be set to the `target` element. - # * `trigger` A {String} that's the same as Bootstrap 'click | hover | focus - # | manual', except 'manual' will show the tooltip immediately. + # a function is passed, `this` will be set to the `target` element. This + # option is mutually exclusive with the `item` option. + # * `html` A {Boolean} affecting the interpetation of the `title` option. + # If `true` (the default), the `title` string will be interpreted as HTML. + # Otherwise it will be interpreted as plain text. + # * `item` A view (object with an `.element` property) or a DOM element + # containing custom content for the tooltip. This option is mutually + # exclusive with the `title` option. + # * `class` A {String} with a class to apply to the tooltip element to + # enable custom styling. + # * `placement` A {String} or {Function} returning a string to indicate + # the position of the tooltip relative to `element`. Can be `'top'`, + # `'bottom'`, `'left'`, `'right'`, or `'auto'`. When `'auto'` is + # specified, it will dynamically reorient the tooltip. For example, if + # placement is `'auto left'`, the tooltip will display to the left when + # possible, otherwise it will display right. + # When a function is used to determine the placement, it is called with + # the tooltip DOM node as its first argument and the triggering element + # DOM node as its second. The `this` context is set to the tooltip + # instance. + # * `trigger` A {String} indicating how the tooltip should be displayed. + # Choose from one of the following options: + # * `'hover'` Show the tooltip when the mouse hovers over the element. + # This is the default. + # * `'click'` Show the tooltip when the element is clicked. The tooltip + # will be hidden after clicking the element again or anywhere else + # outside of the tooltip itself. + # * `'focus'` Show the tooltip when the element is focused. + # * `'manual'` Show the tooltip immediately and only hide it when the + # returned disposable is disposed. + # * `delay` An object specifying the show and hide delay in milliseconds. + # Defaults to `{show: 1000, hide: 100}` if the `trigger` is `hover` and + # otherwise defaults to `0` for both values. # * `keyBindingCommand` A {String} containing a command name. If you specify # this option and a key binding exists that matches the command, it will # be appended to the title or rendered alone if no title is specified. @@ -92,7 +122,12 @@ class TooltipManager else if keystroke? options.title = getKeystroke(bindings) - tooltip = new Tooltip(target, _.defaults(options, @defaults)) + delete options.selector + options = _.defaults(options, @defaults) + if options.trigger is 'hover' + options = _.defaults(options, @hoverDefaults) + + tooltip = new Tooltip(target, options, @viewRegistry) hideTooltip = -> tooltip.leave(currentTarget: target) diff --git a/src/tooltip.js b/src/tooltip.js index ad5ce0cdd..f0f9d1a3f 100644 --- a/src/tooltip.js +++ b/src/tooltip.js @@ -7,13 +7,14 @@ const listen = require('./delegated-listener') // This tooltip class is derived from Bootstrap 3, but modified to not require // jQuery, which is an expensive dependency we want to eliminate. -var Tooltip = function (element, options) { +var Tooltip = function (element, options, viewRegistry) { this.options = null this.enabled = null this.timeout = null this.hoverState = null this.element = null this.inState = null + this.viewRegistry = viewRegistry this.init(element, options) } @@ -64,6 +65,14 @@ Tooltip.prototype.init = function (element, options) { if (trigger === 'click') { this.disposables.add(listen(this.element, 'click', this.options.selector, this.toggle.bind(this))) + this.hideOnClickOutsideOfTooltip = (event) => { + const tooltipElement = this.getTooltipElement() + if (tooltipElement === event.target) return + if (tooltipElement.contains(event.target)) return + if (this.element === event.target) return + if (this.element.contains(event.target)) return + this.hide() + } } else if (trigger === 'manual') { this.show() } else { @@ -182,8 +191,11 @@ Tooltip.prototype.leave = function (event) { Tooltip.prototype.show = function () { if (this.hasContent() && this.enabled) { - var tip = this.getTooltipElement() + if (this.hideOnClickOutsideOfTooltip) { + window.addEventListener('click', this.hideOnClickOutsideOfTooltip, true) + } + var tip = this.getTooltipElement() var tipId = this.getUID('tooltip') this.setContent() @@ -294,19 +306,33 @@ Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) { Tooltip.prototype.setContent = function () { var tip = this.getTooltipElement() - var title = this.getTitle() + + if (this.options.class) { + tip.classList.add(this.options.class) + } var inner = tip.querySelector('.tooltip-inner') - if (this.options.html) { - inner.innerHTML = title + if (this.options.item) { + inner.appendChild(this.viewRegistry.getView(this.options.item)) } else { - inner.textContent = title + var title = this.getTitle() + if (this.options.html) { + inner.innerHTML = title + } else { + inner.textContent = title + } } tip.classList.remove('fade', 'in', 'top', 'bottom', 'left', 'right') } Tooltip.prototype.hide = function (callback) { + this.inState = {} + + if (this.hideOnClickOutsideOfTooltip) { + window.removeEventListener('click', this.hideOnClickOutsideOfTooltip, true) + } + this.tip && this.tip.classList.remove('in') if (this.hoverState !== 'in') this.tip && this.tip.remove() @@ -328,7 +354,7 @@ Tooltip.prototype.fixTitle = function () { } Tooltip.prototype.hasContent = function () { - return this.getTitle() + return this.getTitle() || this.options.item } Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { @@ -436,7 +462,7 @@ Tooltip.prototype.destroy = function () { Tooltip.prototype.getDelegateComponent = function (element) { var component = tooltipComponentsByElement.get(element) if (!component) { - component = new Tooltip(element, this.getDelegateOptions()) + component = new Tooltip(element, this.getDelegateOptions(), this.viewRegistry) tooltipComponentsByElement.set(element, component) } return component diff --git a/src/view-registry.coffee b/src/view-registry.coffee index f5f8651df..e2cd986dc 100644 --- a/src/view-registry.coffee +++ b/src/view-registry.coffee @@ -1,4 +1,3 @@ -{find} = require 'underscore-plus' Grim = require 'grim' {Disposable} = require 'event-kit' _ = require 'underscore-plus' diff --git a/src/window-event-handler.coffee b/src/window-event-handler.coffee index 1a138bcaf..559c8ac29 100644 --- a/src/window-event-handler.coffee +++ b/src/window-event-handler.coffee @@ -1,6 +1,4 @@ -path = require 'path' {Disposable, CompositeDisposable} = require 'event-kit' -fs = require 'fs-plus' listen = require './delegated-listener' # Handles low-level events related to the @window. @@ -10,9 +8,7 @@ class WindowEventHandler @reloadRequested = false @subscriptions = new CompositeDisposable - @previousOnbeforeunloadHandler = @window.onbeforeunload - @window.onbeforeunload = @handleWindowBeforeunload - @addEventListener(@window, 'unload', @handleWindowUnload) + @addEventListener(@window, 'beforeunload', @handleWindowBeforeunload) @addEventListener(@window, 'focus', @handleWindowFocus) @addEventListener(@window, 'blur', @handleWindowBlur) @@ -64,7 +60,6 @@ class WindowEventHandler bindCommandToAction('core:cut', 'cut') unsubscribe: -> - @window.onbeforeunload = @previousOnbeforeunloadHandler @subscriptions.dispose() on: (target, eventName, handler) -> @@ -152,7 +147,7 @@ class WindowEventHandler handleLeaveFullScreen: => @document.body.classList.remove("fullscreen") - handleWindowBeforeunload: => + handleWindowBeforeunload: (event) => confirmed = @atomEnvironment.workspace?.confirmClose(windowCloseRequested: true) if confirmed and not @reloadRequested and not @atomEnvironment.inSpecMode() and @atomEnvironment.getCurrentWindow().isWebViewFocused() @atomEnvironment.hide() @@ -161,14 +156,10 @@ class WindowEventHandler @atomEnvironment.storeWindowDimensions() if confirmed @atomEnvironment.unloadEditorWindow() + @atomEnvironment.destroy() else @applicationDelegate.didCancelWindowUnload() - - # Returning any non-void value stops the window from unloading - return true unless confirmed - - handleWindowUnload: => - @atomEnvironment.destroy() + event.returnValue = false handleWindowToggleFullScreen: => @atomEnvironment.toggleFullScreen() diff --git a/src/window-load-settings-helpers.coffee b/src/window-load-settings-helpers.coffee index 73fd31a3d..639dc751d 100644 --- a/src/window-load-settings-helpers.coffee +++ b/src/window-load-settings-helpers.coffee @@ -1,6 +1,3 @@ -{remote} = require 'electron' -_ = require 'underscore-plus' - windowLoadSettings = null exports.getWindowLoadSettings = -> diff --git a/src/workspace-element.coffee b/src/workspace-element.coffee index 2435c94bf..be0af81ed 100644 --- a/src/workspace-element.coffee +++ b/src/workspace-element.coffee @@ -1,8 +1,7 @@ {ipcRenderer} = require 'electron' path = require 'path' fs = require 'fs-plus' -{Disposable, CompositeDisposable} = require 'event-kit' -Grim = require 'grim' +{CompositeDisposable} = require 'event-kit' scrollbarStyle = require 'scrollbar-style' module.exports = @@ -143,4 +142,13 @@ class WorkspaceElement extends HTMLElement ipcRenderer.send('run-package-specs', specPath) + runBenchmarks: -> + if activePath = @workspace.getActivePaneItem()?.getPath?() + [projectPath] = @project.relativizePath(activePath) + else + [projectPath] = @project.getPaths() + + if projectPath + ipcRenderer.send('run-benchmarks', path.join(projectPath, 'benchmarks')) + module.exports = WorkspaceElement = document.registerElement 'atom-workspace', prototype: WorkspaceElement.prototype diff --git a/src/workspace.coffee b/src/workspace.coffee index aa2b4ae79..89c53b678 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -1,7 +1,6 @@ _ = require 'underscore-plus' url = require 'url' path = require 'path' -{join} = path {Emitter, Disposable, CompositeDisposable} = require 'event-kit' fs = require 'fs-plus' {Directory} = require 'pathwatcher' @@ -9,7 +8,6 @@ DefaultDirectorySearcher = require './default-directory-searcher' Model = require './model' TextEditor = require './text-editor' PaneContainer = require './pane-container' -Pane = require './pane' Panel = require './panel' PanelContainer = require './panel-container' Task = require './task' @@ -30,7 +28,7 @@ class Workspace extends Model { @packageManager, @config, @project, @grammarRegistry, @notificationManager, - @clipboard, @viewRegistry, @grammarRegistry, @applicationDelegate, @assert, + @viewRegistry, @grammarRegistry, @applicationDelegate, @assert, @deserializerManager, @textEditorRegistry } = params @@ -185,6 +183,8 @@ class Workspace extends Model itemPath is projectPath or itemPath?.startsWith(projectPath + path.sep) itemTitle ?= "untitled" projectPath ?= projectPaths[0] + if projectPath? + projectPath = fs.tildify(projectPath) titleParts = [] if item? and projectPath?