mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-11-13 08:44:12 +03:00
Merge branch 'master' into ku-pending-editor
This commit is contained in:
commit
4c1ebdf1b9
17
CHANGELOG.md
17
CHANGELOG.md
@ -1,18 +1 @@
|
||||
See https://atom.io/releases
|
||||
|
||||
## 1.4.0
|
||||
|
||||
* Switching encoding is now fast also with large files.
|
||||
* Fixed an issue where disabling and re-enabling a package caused custom keymaps to be overridden.
|
||||
* Fixed restoring untitled editors on restart. The new behavior never prompts to save new/changed files when closing a window or quitting Atom.
|
||||
|
||||
## 1.3.0
|
||||
|
||||
* The tree-view now sorts directory entries more naturally, in a locale-sensitive way.
|
||||
* Lines can now be moved up and down with multiple cursors.
|
||||
* Improved the performance of marker-dependent code paths such as spell-check and find and replace.
|
||||
* Fixed copying and pasting in native input fields.
|
||||
* By default, windows with no pane items are now closed via the `core:close` command. The previous behavior can be restored via the `Close Empty Windows` option in settings.
|
||||
* Fixed an issue where characters were inserted when toggling the settings view on some keyboard layouts.
|
||||
* Modules can now temporarily override `Error.prepareStackTrace`. There is also an `Error.prototype.getRawStack()` method if you just need access to the raw v8 trace structure.
|
||||
* Fixed a problem that caused blurry fonts on monitors that have a slightly higher resolution than 96 DPI.
|
||||
|
@ -6,6 +6,6 @@
|
||||
"url": "https://github.com/atom/atom.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"atom-package-manager": "1.5.0"
|
||||
"atom-package-manager": "1.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ _ = require 'underscore-plus'
|
||||
packageJson = require '../package.json'
|
||||
|
||||
module.exports = (grunt) ->
|
||||
require('time-grunt')(grunt)
|
||||
|
||||
grunt.loadNpmTasks('grunt-babel')
|
||||
grunt.loadNpmTasks('grunt-coffeelint')
|
||||
grunt.loadNpmTasks('grunt-lesslint')
|
||||
@ -36,7 +38,6 @@ module.exports = (grunt) ->
|
||||
buildDir = grunt.option('build-dir')
|
||||
buildDir ?= path.join(os.tmpdir(), 'atom-build')
|
||||
buildDir = path.resolve(buildDir)
|
||||
disableAutoUpdate = grunt.option('no-auto-update') ? false
|
||||
|
||||
channel = grunt.option('channel')
|
||||
releasableBranches = ['stable', 'beta']
|
||||
@ -179,7 +180,7 @@ module.exports = (grunt) ->
|
||||
pkg: grunt.file.readJSON('package.json')
|
||||
|
||||
atom: {
|
||||
appName, channel, metadata, disableAutoUpdate,
|
||||
appName, channel, metadata,
|
||||
appFileName, apmFileName,
|
||||
appDir, buildDir, contentsDir, installDir, shellAppDir, symbolsDir,
|
||||
}
|
||||
@ -255,7 +256,7 @@ module.exports = (grunt) ->
|
||||
outputDir: 'electron'
|
||||
downloadDir: electronDownloadDir
|
||||
rebuild: true # rebuild native modules after electron is updated
|
||||
token: process.env.ATOM_ACCESS_TOKEN
|
||||
token: process.env.ATOM_ACCESS_TOKEN ? 'da809a6077bb1b0aa7c5623f7b2d5f1fec2faae4'
|
||||
|
||||
'create-windows-installer':
|
||||
installer:
|
||||
|
@ -37,6 +37,7 @@
|
||||
"runas": "^3.1",
|
||||
"tello": "1.0.5",
|
||||
"temp": "~0.8.1",
|
||||
"time-grunt": "1.2.2",
|
||||
"underscore-plus": "1.x",
|
||||
"unzip": "~0.1.9",
|
||||
"vm-compatibility-layer": "~0.1.0",
|
||||
|
@ -186,5 +186,4 @@ module.exports = (grunt) ->
|
||||
dependencies = ['compile', 'generate-license:save', 'generate-module-cache', 'compile-packages-slug']
|
||||
dependencies.push('copy-info-plist') if process.platform is 'darwin'
|
||||
dependencies.push('set-exe-icon') if process.platform is 'win32'
|
||||
dependencies.push('disable-autoupdate') if grunt.config.get('atom.disableAutoUpdate')
|
||||
grunt.task.run(dependencies...)
|
||||
|
@ -1,12 +0,0 @@
|
||||
fs = require 'fs'
|
||||
path = require 'path'
|
||||
|
||||
module.exports = (grunt) ->
|
||||
|
||||
grunt.registerTask 'disable-autoupdate', 'Set up disableAutoUpdate field in package.json file', ->
|
||||
appDir = fs.realpathSync(grunt.config.get('atom.appDir'))
|
||||
|
||||
metadata = grunt.file.readJSON(path.join(appDir, 'package.json'))
|
||||
metadata._disableAutoUpdate = grunt.config.get('atom.disableAutoUpdate')
|
||||
|
||||
grunt.file.write(path.join(appDir, 'package.json'), JSON.stringify(metadata))
|
5
dot-atom/.gitignore
vendored
5
dot-atom/.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
storage
|
||||
blob-store
|
||||
compile-cache
|
||||
dev
|
||||
.npm
|
||||
storage
|
||||
.node-gyp
|
||||
.npm
|
||||
|
@ -13,7 +13,7 @@
|
||||
'ctrl-alt-o': 'application:add-project-folder'
|
||||
'ctrl-shift-pageup': 'pane:move-item-left'
|
||||
'ctrl-shift-pagedown': 'pane:move-item-right'
|
||||
'F11': 'window:toggle-full-screen'
|
||||
'f11': 'window:toggle-full-screen'
|
||||
|
||||
# Sublime Parity
|
||||
'ctrl-,': 'application:show-settings'
|
||||
|
@ -19,7 +19,7 @@
|
||||
'ctrl-alt-o': 'application:add-project-folder'
|
||||
'ctrl-shift-left': 'pane:move-item-left'
|
||||
'ctrl-shift-right': 'pane:move-item-right'
|
||||
'F11': 'window:toggle-full-screen'
|
||||
'f11': 'window:toggle-full-screen'
|
||||
|
||||
# Sublime Parity
|
||||
'ctrl-,': 'application:show-settings'
|
||||
|
@ -11,11 +11,12 @@
|
||||
{ label: 'Downloading Update', enabled: false, visible: false}
|
||||
{ type: 'separator' }
|
||||
{ label: 'Preferences…', command: 'application:show-settings' }
|
||||
{ label: 'Open Your Config', command: 'application:open-your-config' }
|
||||
{ label: 'Open Your Init Script', command: 'application:open-your-init-script' }
|
||||
{ label: 'Open Your Keymap', command: 'application:open-your-keymap' }
|
||||
{ label: 'Open Your Snippets', command: 'application:open-your-snippets' }
|
||||
{ label: 'Open Your Stylesheet', command: 'application:open-your-stylesheet' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Config…', command: 'application:open-your-config' }
|
||||
{ label: 'Init Script…', command: 'application:open-your-init-script' }
|
||||
{ label: 'Keymap…', command: 'application:open-your-keymap' }
|
||||
{ label: 'Snippets…', command: 'application:open-your-snippets' }
|
||||
{ label: 'Stylesheet…', command: 'application:open-your-stylesheet' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Install Shell Commands', command: 'window:install-shell-commands' }
|
||||
{ type: 'separator' }
|
||||
@ -110,36 +111,9 @@
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
label: 'Selection'
|
||||
submenu: [
|
||||
{ label: 'Add Selection Above', command: 'editor:add-selection-above' }
|
||||
{ label: 'Add Selection Below', command: 'editor:add-selection-below' }
|
||||
{ label: 'Single Selection', command: 'editor:consolidate-selections'}
|
||||
{ label: 'Split into Lines', command: 'editor:split-selections-into-lines'}
|
||||
{ type: 'separator' }
|
||||
{ label: 'Select to Top', command: 'core:select-to-top' }
|
||||
{ label: 'Select to Bottom', command: 'core:select-to-bottom' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Select Line', command: 'editor:select-line' }
|
||||
{ label: 'Select Word', command: 'editor:select-word' }
|
||||
{ label: 'Select to Beginning of Word', command: 'editor:select-to-beginning-of-word' }
|
||||
{ label: 'Select to Beginning of Line', command: 'editor:select-to-beginning-of-line' }
|
||||
{ label: 'Select to First Character of Line', command: 'editor:select-to-first-character-of-line' }
|
||||
{ label: 'Select to End of Word', command: 'editor:select-to-end-of-word' }
|
||||
{ label: 'Select to End of Line', command: 'editor:select-to-end-of-line' }
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
label: 'Find'
|
||||
submenu: []
|
||||
}
|
||||
|
||||
{
|
||||
label: 'View'
|
||||
submenu: [
|
||||
{ label: 'Reload', command: 'window:reload' }
|
||||
{ label: 'Toggle Full Screen', command: 'window:toggle-full-screen' }
|
||||
{
|
||||
label: 'Panes'
|
||||
@ -164,6 +138,7 @@
|
||||
label: 'Developer'
|
||||
submenu: [
|
||||
{ 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: 'Toggle Developer Tools', command: 'window:toggle-dev-tools' }
|
||||
]
|
||||
@ -177,6 +152,32 @@
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
label: 'Selection'
|
||||
submenu: [
|
||||
{ label: 'Add Selection Above', command: 'editor:add-selection-above' }
|
||||
{ label: 'Add Selection Below', command: 'editor:add-selection-below' }
|
||||
{ label: 'Single Selection', command: 'editor:consolidate-selections'}
|
||||
{ label: 'Split into Lines', command: 'editor:split-selections-into-lines'}
|
||||
{ type: 'separator' }
|
||||
{ label: 'Select to Top', command: 'core:select-to-top' }
|
||||
{ label: 'Select to Bottom', command: 'core:select-to-bottom' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Select Line', command: 'editor:select-line' }
|
||||
{ label: 'Select Word', command: 'editor:select-word' }
|
||||
{ label: 'Select to Beginning of Word', command: 'editor:select-to-beginning-of-word' }
|
||||
{ label: 'Select to Beginning of Line', command: 'editor:select-to-beginning-of-line' }
|
||||
{ label: 'Select to First Character of Line', command: 'editor:select-to-first-character-of-line' }
|
||||
{ label: 'Select to End of Word', command: 'editor:select-to-end-of-word' }
|
||||
{ label: 'Select to End of Line', command: 'editor:select-to-end-of-line' }
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
label: 'Find'
|
||||
submenu: []
|
||||
}
|
||||
|
||||
{
|
||||
label: 'Packages'
|
||||
submenu: []
|
||||
|
@ -83,11 +83,12 @@
|
||||
}
|
||||
{ type: 'separator' }
|
||||
{ label: '&Preferences', command: 'application:show-settings' }
|
||||
{ label: 'Open Your Config', command: 'application:open-your-config' }
|
||||
{ label: 'Open Your Init Script', command: 'application:open-your-init-script' }
|
||||
{ label: 'Open Your Keymap', command: 'application:open-your-keymap' }
|
||||
{ label: 'Open Your Snippets', command: 'application:open-your-snippets' }
|
||||
{ label: 'Open Your Stylesheet', command: 'application:open-your-stylesheet' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Config…', command: 'application:open-your-config' }
|
||||
{ label: 'Init Script…', command: 'application:open-your-init-script' }
|
||||
{ label: 'Keymap…', command: 'application:open-your-keymap' }
|
||||
{ label: 'Snippets…', command: 'application:open-your-snippets' }
|
||||
{ label: 'Stylesheet…', command: 'application:open-your-stylesheet' }
|
||||
{ type: 'separator' }
|
||||
]
|
||||
}
|
||||
@ -95,7 +96,6 @@
|
||||
{
|
||||
label: '&View'
|
||||
submenu: [
|
||||
{ label: '&Reload', command: 'window:reload' }
|
||||
{ label: 'Toggle &Full Screen', command: 'window:toggle-full-screen' }
|
||||
{ label: 'Toggle Menu Bar', command: 'window:toggle-menu-bar' }
|
||||
{
|
||||
@ -121,6 +121,7 @@
|
||||
label: 'Developer'
|
||||
submenu: [
|
||||
{ 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: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' }
|
||||
]
|
||||
|
@ -10,11 +10,12 @@
|
||||
{ label: 'Reopen Last &Item', command: 'pane:reopen-closed-item' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Se&ttings', command: 'application:show-settings' }
|
||||
{ label: 'Open Your Config', command: 'application:open-your-config' }
|
||||
{ label: 'Open Your Init Script', command: 'application:open-your-init-script' }
|
||||
{ label: 'Open Your Keymap', command: 'application:open-your-keymap' }
|
||||
{ label: 'Open Your Snippets', command: 'application:open-your-snippets' }
|
||||
{ label: 'Open Your Stylesheet', command: 'application:open-your-stylesheet' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Config…', command: 'application:open-your-config' }
|
||||
{ label: 'Init Script…', command: 'application:open-your-init-script' }
|
||||
{ label: 'Keymap…', command: 'application:open-your-keymap' }
|
||||
{ label: 'Snippets…', command: 'application:open-your-snippets' }
|
||||
{ label: 'Stylesheet…', command: 'application:open-your-stylesheet' }
|
||||
{ type: 'separator' }
|
||||
{ label: '&Save', command: 'core:save' }
|
||||
{ label: 'Save &As…', command: 'core:save-as' }
|
||||
@ -94,7 +95,6 @@
|
||||
{
|
||||
label: '&View'
|
||||
submenu: [
|
||||
{ label: '&Reload', command: 'window:reload' }
|
||||
{ label: 'Toggle &Full Screen', command: 'window:toggle-full-screen' }
|
||||
{ label: 'Toggle Menu Bar', command: 'window:toggle-menu-bar' }
|
||||
{
|
||||
@ -120,6 +120,7 @@
|
||||
label: 'Developer'
|
||||
submenu: [
|
||||
{ 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: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' }
|
||||
]
|
||||
|
44
package.json
44
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "atom",
|
||||
"productName": "Atom",
|
||||
"version": "1.5.0-dev",
|
||||
"version": "1.6.0-dev",
|
||||
"description": "A hackable text editor for the 21st Century.",
|
||||
"main": "./src/browser/main.js",
|
||||
"repository": {
|
||||
@ -28,13 +28,14 @@
|
||||
"fs-plus": "^2.8.0",
|
||||
"fstream": "0.1.24",
|
||||
"fuzzaldrin": "^2.1",
|
||||
"git-utils": "^4.0.7",
|
||||
"git-utils": "^4.1.0",
|
||||
"grim": "1.5.0",
|
||||
"jasmine-json": "~0.0",
|
||||
"jasmine-tagged": "^1.1.4",
|
||||
"jquery": "^2.1.1",
|
||||
"jquery": "2.1.4",
|
||||
"key-path-helpers": "^0.4.0",
|
||||
"less-cache": "0.22",
|
||||
"line-top-index": "0.2.0",
|
||||
"marked": "^0.3.4",
|
||||
"normalize-package-data": "^2.0.0",
|
||||
"nslog": "^3",
|
||||
@ -52,7 +53,7 @@
|
||||
"service-hub": "^0.7.0",
|
||||
"source-map-support": "^0.3.2",
|
||||
"temp": "0.8.1",
|
||||
"text-buffer": "8.1.3",
|
||||
"text-buffer": "8.1.4",
|
||||
"typescript-simple": "1.0.0",
|
||||
"underscore-plus": "^1.6.6",
|
||||
"yargs": "^3.23.0"
|
||||
@ -65,12 +66,12 @@
|
||||
"base16-tomorrow-dark-theme": "1.1.0",
|
||||
"base16-tomorrow-light-theme": "1.1.1",
|
||||
"one-dark-ui": "1.1.9",
|
||||
"one-dark-syntax": "1.1.1",
|
||||
"one-light-syntax": "1.1.1",
|
||||
"one-light-ui": "1.1.9",
|
||||
"one-dark-syntax": "1.1.2",
|
||||
"one-light-syntax": "1.1.2",
|
||||
"solarized-dark-syntax": "0.39.0",
|
||||
"solarized-light-syntax": "0.23.0",
|
||||
"about": "1.1.0",
|
||||
"about": "1.3.0",
|
||||
"archive-view": "0.61.0",
|
||||
"autocomplete-atom-api": "0.9.2",
|
||||
"autocomplete-css": "0.11.0",
|
||||
@ -80,7 +81,7 @@
|
||||
"autoflow": "0.26.0",
|
||||
"autosave": "0.23.0",
|
||||
"background-tips": "0.26.0",
|
||||
"bookmarks": "0.38.0",
|
||||
"bookmarks": "0.38.2",
|
||||
"bracket-matcher": "0.79.0",
|
||||
"command-palette": "0.38.0",
|
||||
"deprecation-cop": "0.54.0",
|
||||
@ -102,16 +103,15 @@
|
||||
"notifications": "0.62.1",
|
||||
"open-on-github": "0.40.0",
|
||||
"package-generator": "0.41.0",
|
||||
"release-notes": "0.53.0",
|
||||
"settings-view": "0.232.1",
|
||||
"settings-view": "0.232.3",
|
||||
"snippets": "1.0.1",
|
||||
"spell-check": "0.63.0",
|
||||
"spell-check": "0.65.0",
|
||||
"status-bar": "0.80.0",
|
||||
"styleguide": "0.45.0",
|
||||
"symbols-view": "0.110.1",
|
||||
"tabs": "0.88.0",
|
||||
"timecop": "0.33.0",
|
||||
"tree-view": "0.198.0",
|
||||
"tree-view": "0.198.1",
|
||||
"update-package-dependencies": "0.10.0",
|
||||
"welcome": "0.33.0",
|
||||
"whitespace": "0.32.1",
|
||||
@ -121,24 +121,24 @@
|
||||
"language-coffee-script": "0.46.0",
|
||||
"language-csharp": "0.11.0",
|
||||
"language-css": "0.36.0",
|
||||
"language-gfm": "0.82.0",
|
||||
"language-git": "0.11.0",
|
||||
"language-go": "0.41.0",
|
||||
"language-html": "0.43.1",
|
||||
"language-gfm": "0.83.0",
|
||||
"language-git": "0.12.1",
|
||||
"language-go": "0.42.0",
|
||||
"language-html": "0.44.0",
|
||||
"language-hyperlink": "0.16.0",
|
||||
"language-java": "0.17.0",
|
||||
"language-javascript": "0.104.0",
|
||||
"language-json": "0.17.2",
|
||||
"language-javascript": "0.105.0",
|
||||
"language-json": "0.17.3",
|
||||
"language-less": "0.29.0",
|
||||
"language-make": "0.21.0",
|
||||
"language-mustache": "0.13.0",
|
||||
"language-objective-c": "0.15.1",
|
||||
"language-perl": "0.32.0",
|
||||
"language-php": "0.34.0",
|
||||
"language-php": "0.36.0",
|
||||
"language-property-list": "0.8.0",
|
||||
"language-python": "0.42.1",
|
||||
"language-ruby": "0.65.0",
|
||||
"language-ruby-on-rails": "0.24.0",
|
||||
"language-python": "0.43.0",
|
||||
"language-ruby": "0.68.0",
|
||||
"language-ruby-on-rails": "0.25.0",
|
||||
"language-sass": "0.45.0",
|
||||
"language-shellscript": "0.21.0",
|
||||
"language-source": "0.9.0",
|
||||
|
@ -5,8 +5,16 @@ var verifyRequirements = require('./utils/verify-requirements');
|
||||
var safeExec = require('./utils/child-process-wrapper.js').safeExec;
|
||||
var path = require('path');
|
||||
|
||||
var t0, t1
|
||||
|
||||
// Executes an array of commands one by one.
|
||||
function executeCommands(commands, done, index) {
|
||||
if (index != undefined) {
|
||||
t1 = Date.now()
|
||||
console.log("=> Took " + (t1 - t0) + "ms.");
|
||||
console.log();
|
||||
}
|
||||
|
||||
index = (index == undefined ? 0 : index);
|
||||
if (index < commands.length) {
|
||||
var command = commands[index];
|
||||
@ -17,6 +25,7 @@ function executeCommands(commands, done, index) {
|
||||
options = command.options;
|
||||
command = command.command;
|
||||
}
|
||||
t0 = Date.now()
|
||||
safeExec(command, options, executeCommands.bind(this, commands, done, index + 1));
|
||||
}
|
||||
else
|
||||
@ -96,7 +105,10 @@ function bootstrap() {
|
||||
message: 'Installing apm...',
|
||||
options: apmInstallOptions
|
||||
},
|
||||
apmPath + ' clean' + apmFlags,
|
||||
{
|
||||
command: apmPath + ' clean' + apmFlags,
|
||||
message: 'Deleting old packages...'
|
||||
},
|
||||
moduleInstallCommand,
|
||||
dedupeApmCommand + ' ' + packagesToDedupe.join(' '),
|
||||
];
|
||||
|
@ -1,4 +1,26 @@
|
||||
# Users may have this environment variable set. Currently, it causes babel to
|
||||
# log to stderr, which causes errors on Windows.
|
||||
# See https://github.com/atom/electron/issues/2033
|
||||
process.env.DEBUG='*'
|
||||
|
||||
path = require('path')
|
||||
temp = require('temp').track()
|
||||
CompileCache = require('../src/compile-cache')
|
||||
|
||||
describe "Babel transpiler support", ->
|
||||
originalCacheDir = null
|
||||
|
||||
beforeEach ->
|
||||
originalCacheDir = CompileCache.getCacheDirectory()
|
||||
CompileCache.setCacheDirectory(temp.mkdirSync('compile-cache'))
|
||||
for cacheKey in Object.keys(require.cache)
|
||||
if cacheKey.startsWith(path.join(__dirname, 'fixtures', 'babel'))
|
||||
console.log('deleting', cacheKey)
|
||||
delete require.cache[cacheKey]
|
||||
|
||||
afterEach ->
|
||||
CompileCache.setCacheDirectory(originalCacheDir)
|
||||
|
||||
describe 'when a .js file starts with /** @babel */;', ->
|
||||
it "transpiles it using babel", ->
|
||||
transpiled = require('./fixtures/babel/babel-comment.js')
|
||||
@ -17,3 +39,12 @@ describe "Babel transpiler support", ->
|
||||
describe "when a .js file does not start with 'use babel';", ->
|
||||
it "does not transpile it using babel", ->
|
||||
expect(-> require('./fixtures/babel/invalid.js')).toThrow()
|
||||
|
||||
it "does not try to log to stdout or stderr while parsing the file", ->
|
||||
spyOn(process.stderr, 'write')
|
||||
spyOn(process.stdout, 'write')
|
||||
|
||||
transpiled = require('./fixtures/babel/babel-double-quotes.js')
|
||||
|
||||
expect(process.stdout.write).not.toHaveBeenCalled()
|
||||
expect(process.stderr.write).not.toHaveBeenCalled()
|
||||
|
@ -872,6 +872,26 @@ describe "Config", ->
|
||||
atom.config.loadUserConfig()
|
||||
expect(atom.config.get("foo.bar")).toBe "baz"
|
||||
|
||||
describe "when the config file fails to load", ->
|
||||
addErrorHandler = null
|
||||
|
||||
beforeEach ->
|
||||
atom.notifications.onDidAddNotification addErrorHandler = jasmine.createSpy()
|
||||
spyOn(fs, "existsSync").andCallFake ->
|
||||
error = new Error()
|
||||
error.code = 'EPERM'
|
||||
throw error
|
||||
|
||||
it "creates a notification and does not try to save later changes to disk", ->
|
||||
load = -> atom.config.loadUserConfig()
|
||||
expect(load).not.toThrow()
|
||||
expect(addErrorHandler.callCount).toBe 1
|
||||
|
||||
atom.config.set("foo.bar", "baz")
|
||||
advanceClock(100)
|
||||
expect(atom.config.save).not.toHaveBeenCalled()
|
||||
expect(atom.config.get("foo.bar")).toBe "baz"
|
||||
|
||||
describe ".observeUserConfig()", ->
|
||||
updatedHandler = null
|
||||
|
||||
|
@ -1253,6 +1253,13 @@ describe "DisplayBuffer", ->
|
||||
decoration.destroy()
|
||||
expect(displayBuffer.decorationForId(decoration.id)).not.toBeDefined()
|
||||
|
||||
it "does not allow destroyed markers to be decorated", ->
|
||||
marker.destroy()
|
||||
expect(->
|
||||
displayBuffer.decorateMarker(marker, {type: 'overlay', item: document.createElement('div')})
|
||||
).toThrow("Cannot decorate a destroyed marker")
|
||||
expect(displayBuffer.getOverlayDecorations()).toEqual []
|
||||
|
||||
describe "when a decoration is updated via Decoration::update()", ->
|
||||
it "emits an 'updated' event containing the new and old params", ->
|
||||
decoration.onDidChangeProperties updatedSpy = jasmine.createSpy()
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
module.exports =
|
||||
class FakeLinesYardstick
|
||||
constructor: (@model) ->
|
||||
constructor: (@model, @lineTopIndex) ->
|
||||
@characterWidthsByScope = {}
|
||||
|
||||
getScopedCharacterWidth: (scopeNames, char) ->
|
||||
@ -19,15 +19,14 @@ class FakeLinesYardstick
|
||||
setScopedCharacterWidth: (scopeNames, character, width) ->
|
||||
@getScopedCharacterWidths(scopeNames)[character] = width
|
||||
|
||||
pixelPositionForScreenPosition: (screenPosition, clip=true) ->
|
||||
pixelPositionForScreenPosition: (screenPosition) ->
|
||||
screenPosition = Point.fromObject(screenPosition)
|
||||
screenPosition = @model.clipScreenPosition(screenPosition) if clip
|
||||
|
||||
targetRow = screenPosition.row
|
||||
targetColumn = screenPosition.column
|
||||
baseCharacterWidth = @model.getDefaultCharWidth()
|
||||
|
||||
top = targetRow * @model.getLineHeightInPixels()
|
||||
top = @lineTopIndex.pixelPositionAfterBlocksForRow(targetRow)
|
||||
left = 0
|
||||
column = 0
|
||||
|
||||
|
@ -125,6 +125,27 @@ describe "Starting Atom", ->
|
||||
.treeViewRootDirectories()
|
||||
.then ({value}) -> expect(value).toEqual([otherTempDirPath])
|
||||
|
||||
it "opens the new window offset from the other window", ->
|
||||
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
|
||||
win0Position = null
|
||||
win1Position = null
|
||||
client
|
||||
.waitForWindowCount(1, 10000)
|
||||
.execute -> atom.getPosition()
|
||||
.then ({value}) -> win0Position = value
|
||||
.waitForNewWindow(->
|
||||
@startAnotherAtom([path.join(temp.mkdirSync("a-third-dir"), "a-file")], ATOM_HOME: atomHome)
|
||||
, 5000)
|
||||
.waitForWindowCount(2, 10000)
|
||||
.execute -> atom.getPosition()
|
||||
.then ({value}) -> win1Position = value
|
||||
.then ->
|
||||
expect(win1Position.x).toBeGreaterThan(win0Position.x)
|
||||
# Ideally we'd test the y coordinate too, but if the window's
|
||||
# already as tall as it can be, then OS X won't move it down outside
|
||||
# the screen.
|
||||
# expect(win1Position.y).toBeGreaterThan(win0Position.y)
|
||||
|
||||
describe "reopening a directory that was previously opened", ->
|
||||
it "remembers the state of the window", ->
|
||||
runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) ->
|
||||
|
@ -1,5 +1,7 @@
|
||||
LinesYardstick = require "../src/lines-yardstick"
|
||||
LinesYardstick = require '../src/lines-yardstick'
|
||||
LineTopIndex = require 'line-top-index'
|
||||
{toArray} = require 'underscore-plus'
|
||||
{Point} = require 'text-buffer'
|
||||
|
||||
describe "LinesYardstick", ->
|
||||
[editor, mockLineNodesProvider, createdLineNodes, linesYardstick, buildLineNode] = []
|
||||
@ -44,7 +46,10 @@ describe "LinesYardstick", ->
|
||||
textNodes
|
||||
|
||||
editor.setLineHeightInPixels(14)
|
||||
linesYardstick = new LinesYardstick(editor, mockLineNodesProvider, atom.grammars)
|
||||
lineTopIndex = new LineTopIndex({
|
||||
defaultLineHeight: editor.getLineHeightInPixels()
|
||||
})
|
||||
linesYardstick = new LinesYardstick(editor, mockLineNodesProvider, lineTopIndex, atom.grammars)
|
||||
|
||||
afterEach ->
|
||||
lineNode.remove() for lineNode in createdLineNodes
|
||||
@ -62,12 +67,12 @@ describe "LinesYardstick", ->
|
||||
}
|
||||
"""
|
||||
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([0, 0])).toEqual({left: 0, top: 0})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([0, 1])).toEqual({left: 7, top: 0})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([0, 5])).toEqual({left: 37.78125, top: 0})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([1, 6])).toEqual({left: 43.171875, top: 14})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([1, 9])).toEqual({left: 72.171875, top: 14})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([2, Infinity])).toEqual({left: 287.859375, top: 28})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 0))).toEqual({left: 0, top: 0})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 1))).toEqual({left: 7, top: 0})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 5))).toEqual({left: 37.78125, top: 0})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 6))).toEqual({left: 43.171875, top: 14})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 9))).toEqual({left: 72.171875, top: 14})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(2, Infinity))).toEqual({left: 287.859375, top: 28})
|
||||
|
||||
it "reuses already computed pixel positions unless it is invalidated", ->
|
||||
atom.styles.addStyleSheet """
|
||||
@ -77,9 +82,9 @@ describe "LinesYardstick", ->
|
||||
}
|
||||
"""
|
||||
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([1, 2])).toEqual({left: 19.203125, top: 14})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([2, 6])).toEqual({left: 57.609375, top: 28})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([5, 10])).toEqual({left: 95.609375, top: 70})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 2))).toEqual({left: 19.203125, top: 14})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(2, 6))).toEqual({left: 57.609375, top: 28})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(5, 10))).toEqual({left: 95.609375, top: 70})
|
||||
|
||||
atom.styles.addStyleSheet """
|
||||
* {
|
||||
@ -87,15 +92,15 @@ describe "LinesYardstick", ->
|
||||
}
|
||||
"""
|
||||
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([1, 2])).toEqual({left: 19.203125, top: 14})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([2, 6])).toEqual({left: 57.609375, top: 28})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([5, 10])).toEqual({left: 95.609375, top: 70})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 2))).toEqual({left: 19.203125, top: 14})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(2, 6))).toEqual({left: 57.609375, top: 28})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(5, 10))).toEqual({left: 95.609375, top: 70})
|
||||
|
||||
linesYardstick.invalidateCache()
|
||||
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([1, 2])).toEqual({left: 24, top: 14})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([2, 6])).toEqual({left: 72, top: 28})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([5, 10])).toEqual({left: 120, top: 70})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 2))).toEqual({left: 24, top: 14})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(2, 6))).toEqual({left: 72, top: 28})
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(5, 10))).toEqual({left: 120, top: 70})
|
||||
|
||||
it "correctly handles RTL characters", ->
|
||||
atom.styles.addStyleSheet """
|
||||
@ -106,13 +111,13 @@ describe "LinesYardstick", ->
|
||||
"""
|
||||
|
||||
editor.setText("السلام عليكم")
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([0, 0]).left).toBe 0
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([0, 1]).left).toBe 8
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([0, 2]).left).toBe 16
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([0, 5]).left).toBe 33
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([0, 7]).left).toBe 50
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([0, 9]).left).toBe 67
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([0, 11]).left).toBe 84
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 0)).left).toBe 0
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 1)).left).toBe 8
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 2)).left).toBe 16
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 5)).left).toBe 33
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 7)).left).toBe 50
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 9)).left).toBe 67
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 11)).left).toBe 84
|
||||
|
||||
it "doesn't report a width greater than 0 when the character to measure is at the beginning of a text node", ->
|
||||
# This spec documents what seems to be a bug in Chromium, because we'd
|
||||
@ -137,9 +142,9 @@ describe "LinesYardstick", ->
|
||||
|
||||
editor.setText(text)
|
||||
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([0, 35]).left).toBe 230.90625
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([0, 36]).left).toBe 237.5
|
||||
expect(linesYardstick.pixelPositionForScreenPosition([0, 37]).left).toBe 244.09375
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 35)).left).toBe 230.90625
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 36)).left).toBe 237.5
|
||||
expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 37)).left).toBe 244.09375
|
||||
|
||||
describe "::screenPositionForPixelPosition(pixelPosition)", ->
|
||||
it "converts pixel positions to screen positions", ->
|
||||
|
@ -1026,6 +1026,16 @@ describe "PackageManager", ->
|
||||
expect(atom.packages.enablePackage("this-doesnt-exist")).toBeNull()
|
||||
expect(console.warn.callCount).toBe 1
|
||||
|
||||
it "does not disable an already disabled package", ->
|
||||
packageName = 'package-with-main'
|
||||
atom.config.pushAtKeyPath('core.disabledPackages', packageName)
|
||||
atom.packages.observeDisabledPackages()
|
||||
expect(atom.config.get('core.disabledPackages')).toContain packageName
|
||||
|
||||
atom.packages.disablePackage(packageName)
|
||||
packagesDisabled = atom.config.get('core.disabledPackages').filter((pack) -> pack is packageName)
|
||||
expect(packagesDisabled.length).toEqual 1
|
||||
|
||||
describe "with themes", ->
|
||||
didChangeActiveThemesHandler = null
|
||||
|
||||
|
@ -1651,6 +1651,214 @@ describe('TextEditorComponent', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('block decorations rendering', function () {
|
||||
function createBlockDecorationBeforeScreenRow(screenRow, {className}) {
|
||||
let item = document.createElement("div")
|
||||
item.className = className || ""
|
||||
let blockDecoration = editor.decorateMarker(
|
||||
editor.markScreenPosition([screenRow, 0], {invalidate: "never"}),
|
||||
{type: "block", item: item, position: "before"}
|
||||
)
|
||||
return [item, blockDecoration]
|
||||
}
|
||||
|
||||
function createBlockDecorationAfterScreenRow(screenRow, {className}) {
|
||||
let item = document.createElement("div")
|
||||
item.className = className || ""
|
||||
let blockDecoration = editor.decorateMarker(
|
||||
editor.markScreenPosition([screenRow, 0], {invalidate: "never"}),
|
||||
{type: "block", item: item, position: "after"}
|
||||
)
|
||||
return [item, blockDecoration]
|
||||
}
|
||||
|
||||
beforeEach(async function () {
|
||||
wrapperNode.style.height = 5 * lineHeightInPixels + 'px'
|
||||
component.measureDimensions()
|
||||
await nextViewUpdatePromise()
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
atom.themes.removeStylesheet('test')
|
||||
})
|
||||
|
||||
it("renders visible and yet-to-be-measured block decorations, inserting them between the appropriate lines and refreshing them as needed", async function () {
|
||||
let [item1, blockDecoration1] = createBlockDecorationBeforeScreenRow(0, {className: "decoration-1"})
|
||||
let [item2, blockDecoration2] = createBlockDecorationBeforeScreenRow(2, {className: "decoration-2"})
|
||||
let [item3, blockDecoration3] = createBlockDecorationBeforeScreenRow(4, {className: "decoration-3"})
|
||||
let [item4, blockDecoration4] = createBlockDecorationBeforeScreenRow(7, {className: "decoration-4"})
|
||||
let [item5, blockDecoration5] = createBlockDecorationAfterScreenRow(7, {className: "decoration-5"})
|
||||
|
||||
atom.styles.addStyleSheet(
|
||||
`atom-text-editor .decoration-1 { width: 30px; height: 80px; }
|
||||
atom-text-editor .decoration-2 { width: 30px; height: 40px; }
|
||||
atom-text-editor .decoration-3 { width: 30px; height: 100px; }
|
||||
atom-text-editor .decoration-4 { width: 30px; height: 120px; }
|
||||
atom-text-editor .decoration-5 { width: 30px; height: 42px; }`,
|
||||
{context: 'atom-text-editor'}
|
||||
)
|
||||
await nextAnimationFramePromise()
|
||||
|
||||
expect(component.getDomNode().querySelectorAll(".line").length).toBe(7)
|
||||
|
||||
expect(component.tileNodesForLines()[0].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 80 + 40 + "px")
|
||||
expect(component.tileNodesForLines()[0].style.webkitTransform).toBe("translate3d(0px, 0px, 0px)")
|
||||
expect(component.tileNodesForLines()[1].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 100 + "px")
|
||||
expect(component.tileNodesForLines()[1].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight}px, 0px)`)
|
||||
expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 120 + 42 + "px")
|
||||
expect(component.tileNodesForLines()[2].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight + component.tileNodesForLines()[1].offsetHeight}px, 0px)`)
|
||||
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-1")).toBe(item1)
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-2")).toBe(item2)
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-3")).toBe(item3)
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-4")).toBeNull()
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-5")).toBeNull()
|
||||
|
||||
expect(item1.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 0)
|
||||
expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 2 + 80)
|
||||
expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 4 + 80 + 40)
|
||||
|
||||
editor.setCursorScreenPosition([0, 0])
|
||||
editor.insertNewline()
|
||||
blockDecoration1.destroy()
|
||||
|
||||
await nextAnimationFramePromise()
|
||||
|
||||
expect(component.getDomNode().querySelectorAll(".line").length).toBe(7)
|
||||
|
||||
expect(component.tileNodesForLines()[0].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + "px")
|
||||
expect(component.tileNodesForLines()[0].style.webkitTransform).toBe("translate3d(0px, 0px, 0px)")
|
||||
expect(component.tileNodesForLines()[1].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 100 + 40 + "px")
|
||||
expect(component.tileNodesForLines()[1].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight}px, 0px)`)
|
||||
expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 120 + 42 + "px")
|
||||
expect(component.tileNodesForLines()[2].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight + component.tileNodesForLines()[1].offsetHeight}px, 0px)`)
|
||||
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-1")).toBeNull()
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-2")).toBe(item2)
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-3")).toBe(item3)
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-4")).toBeNull()
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-5")).toBeNull()
|
||||
|
||||
expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3)
|
||||
expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 40)
|
||||
|
||||
atom.styles.addStyleSheet(
|
||||
'atom-text-editor .decoration-2 { height: 60px; }',
|
||||
{context: 'atom-text-editor'}
|
||||
)
|
||||
|
||||
await nextAnimationFramePromise() // causes the DOM to update and to retrieve new styles
|
||||
await nextAnimationFramePromise() // applies the changes
|
||||
|
||||
expect(component.getDomNode().querySelectorAll(".line").length).toBe(7)
|
||||
|
||||
expect(component.tileNodesForLines()[0].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + "px")
|
||||
expect(component.tileNodesForLines()[0].style.webkitTransform).toBe("translate3d(0px, 0px, 0px)")
|
||||
expect(component.tileNodesForLines()[1].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 100 + 60 + "px")
|
||||
expect(component.tileNodesForLines()[1].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight}px, 0px)`)
|
||||
expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 120 + 42 + "px")
|
||||
expect(component.tileNodesForLines()[2].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight + component.tileNodesForLines()[1].offsetHeight}px, 0px)`)
|
||||
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-1")).toBeNull()
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-2")).toBe(item2)
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-3")).toBe(item3)
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-4")).toBeNull()
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-5")).toBeNull()
|
||||
|
||||
expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3)
|
||||
expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 60)
|
||||
|
||||
item2.style.height = "20px"
|
||||
wrapperNode.invalidateBlockDecorationDimensions(blockDecoration2)
|
||||
await nextAnimationFramePromise()
|
||||
await nextAnimationFramePromise()
|
||||
|
||||
expect(component.getDomNode().querySelectorAll(".line").length).toBe(9)
|
||||
|
||||
expect(component.tileNodesForLines()[0].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + "px")
|
||||
expect(component.tileNodesForLines()[0].style.webkitTransform).toBe("translate3d(0px, 0px, 0px)")
|
||||
expect(component.tileNodesForLines()[1].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 100 + 20 + "px")
|
||||
expect(component.tileNodesForLines()[1].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight}px, 0px)`)
|
||||
expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 120 + 42 + "px")
|
||||
expect(component.tileNodesForLines()[2].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight + component.tileNodesForLines()[1].offsetHeight}px, 0px)`)
|
||||
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-1")).toBeNull()
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-2")).toBe(item2)
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-3")).toBe(item3)
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-4")).toBe(item4)
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-5")).toBe(item5)
|
||||
|
||||
expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3)
|
||||
expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 20)
|
||||
expect(item4.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 8 + 20 + 100)
|
||||
expect(item5.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 8 + 20 + 100 + 120 + lineHeightInPixels)
|
||||
})
|
||||
|
||||
it("correctly sets screen rows on <content> elements, both initially and when decorations move", async function () {
|
||||
let [item, blockDecoration] = createBlockDecorationBeforeScreenRow(0, {className: "decoration-1"})
|
||||
atom.styles.addStyleSheet(
|
||||
'atom-text-editor .decoration-1 { width: 30px; height: 80px; }',
|
||||
{context: 'atom-text-editor'}
|
||||
)
|
||||
|
||||
await nextAnimationFramePromise()
|
||||
|
||||
let tileNode, contentElements
|
||||
|
||||
tileNode = component.tileNodesForLines()[0]
|
||||
contentElements = tileNode.querySelectorAll("content")
|
||||
|
||||
expect(contentElements.length).toBe(1)
|
||||
expect(contentElements[0].dataset.screenRow).toBe("0")
|
||||
expect(component.lineNodeForScreenRow(0).dataset.screenRow).toBe("0")
|
||||
expect(component.lineNodeForScreenRow(1).dataset.screenRow).toBe("1")
|
||||
expect(component.lineNodeForScreenRow(2).dataset.screenRow).toBe("2")
|
||||
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
editor.insertNewline()
|
||||
await nextAnimationFramePromise()
|
||||
|
||||
tileNode = component.tileNodesForLines()[0]
|
||||
contentElements = tileNode.querySelectorAll("content")
|
||||
|
||||
expect(contentElements.length).toBe(1)
|
||||
expect(contentElements[0].dataset.screenRow).toBe("1")
|
||||
expect(component.lineNodeForScreenRow(0).dataset.screenRow).toBe("0")
|
||||
expect(component.lineNodeForScreenRow(1).dataset.screenRow).toBe("1")
|
||||
expect(component.lineNodeForScreenRow(2).dataset.screenRow).toBe("2")
|
||||
|
||||
blockDecoration.getMarker().setHeadBufferPosition([2, 0])
|
||||
await nextAnimationFramePromise()
|
||||
|
||||
tileNode = component.tileNodesForLines()[0]
|
||||
contentElements = tileNode.querySelectorAll("content")
|
||||
|
||||
expect(contentElements.length).toBe(1)
|
||||
expect(contentElements[0].dataset.screenRow).toBe("2")
|
||||
expect(component.lineNodeForScreenRow(0).dataset.screenRow).toBe("0")
|
||||
expect(component.lineNodeForScreenRow(1).dataset.screenRow).toBe("1")
|
||||
expect(component.lineNodeForScreenRow(2).dataset.screenRow).toBe("2")
|
||||
})
|
||||
|
||||
it('measures block decorations taking into account both top and bottom margins', async function () {
|
||||
let [item, blockDecoration] = createBlockDecorationBeforeScreenRow(0, {className: "decoration-1"})
|
||||
atom.styles.addStyleSheet(
|
||||
'atom-text-editor .decoration-1 { width: 30px; height: 30px; margin-top: 10px; margin-bottom: 5px; }',
|
||||
{context: 'atom-text-editor'}
|
||||
)
|
||||
|
||||
await nextAnimationFramePromise() // causes the DOM to update and to retrieve new styles
|
||||
await nextAnimationFramePromise() // applies the changes
|
||||
|
||||
expect(component.tileNodesForLines()[0].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 30 + 10 + 5 + "px")
|
||||
expect(component.tileNodesForLines()[0].style.webkitTransform).toBe("translate3d(0px, 0px, 0px)")
|
||||
expect(component.tileNodesForLines()[1].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + "px")
|
||||
expect(component.tileNodesForLines()[1].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight}px, 0px)`)
|
||||
expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + "px")
|
||||
expect(component.tileNodesForLines()[2].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight + component.tileNodesForLines()[1].offsetHeight}px, 0px)`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('highlight decoration rendering', function () {
|
||||
let decoration, marker, scrollViewClientLeft
|
||||
|
||||
@ -3433,6 +3641,40 @@ describe('TextEditorComponent', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the mousewheel event\'s target is a block decoration', function () {
|
||||
it('keeps it on the DOM if it is scrolled off-screen', async function () {
|
||||
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
wrapperNode.style.width = 20 * charWidth + 'px'
|
||||
component.measureDimensions()
|
||||
await nextViewUpdatePromise()
|
||||
|
||||
let item = document.createElement("div")
|
||||
item.style.width = "30px"
|
||||
item.style.height = "30px"
|
||||
item.className = "decoration-1"
|
||||
editor.decorateMarker(
|
||||
editor.markScreenPosition([0, 0], {invalidate: "never"}),
|
||||
{type: "block", item: item}
|
||||
)
|
||||
|
||||
await nextViewUpdatePromise()
|
||||
|
||||
let wheelEvent = new WheelEvent('mousewheel', {
|
||||
wheelDeltaX: 0,
|
||||
wheelDeltaY: -500
|
||||
})
|
||||
Object.defineProperty(wheelEvent, 'target', {
|
||||
get: function () {
|
||||
return item
|
||||
}
|
||||
})
|
||||
componentNode.dispatchEvent(wheelEvent)
|
||||
await nextAnimationFramePromise()
|
||||
|
||||
expect(component.getTopmostDOMNode().contains(item)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
it('only prevents the default action of the mousewheel event if it actually lead to scrolling', async function () {
|
||||
spyOn(WheelEvent.prototype, 'preventDefault').andCallThrough()
|
||||
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
@ -4567,6 +4809,13 @@ describe('TextEditorComponent', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('::pixelPositionForScreenPosition()', () => {
|
||||
it('returns the correct horizontal position, even if it is on a row that has not yet been rendered (regression)', () => {
|
||||
editor.setTextInBufferRange([[5, 0], [6, 0]], 'hello world\n')
|
||||
expect(wrapperNode.pixelPositionForScreenPosition([5, Infinity]).left).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('middle mouse paste on Linux', function () {
|
||||
let originalPlatform
|
||||
|
||||
|
@ -5,6 +5,7 @@ TextBuffer = require 'text-buffer'
|
||||
TextEditor = require '../src/text-editor'
|
||||
TextEditorPresenter = require '../src/text-editor-presenter'
|
||||
FakeLinesYardstick = require './fake-lines-yardstick'
|
||||
LineTopIndex = require 'line-top-index'
|
||||
|
||||
describe "TextEditorPresenter", ->
|
||||
# These `describe` and `it` blocks mirror the structure of the ::state object.
|
||||
@ -29,13 +30,29 @@ describe "TextEditorPresenter", ->
|
||||
presenter.getPreMeasurementState()
|
||||
presenter.getPostMeasurementState()
|
||||
|
||||
addBlockDecorationBeforeScreenRow = (screenRow, item) ->
|
||||
editor.decorateMarker(
|
||||
editor.markScreenPosition([screenRow, 0], invalidate: "never"),
|
||||
type: "block", item: item, position: "before"
|
||||
)
|
||||
|
||||
addBlockDecorationAfterScreenRow = (screenRow, item) ->
|
||||
editor.decorateMarker(
|
||||
editor.markScreenPosition([screenRow, 0], invalidate: "never"),
|
||||
type: "block", item: item, position: "after"
|
||||
)
|
||||
|
||||
buildPresenterWithoutMeasurements = (params={}) ->
|
||||
lineTopIndex = new LineTopIndex({
|
||||
defaultLineHeight: editor.getLineHeightInPixels()
|
||||
})
|
||||
_.defaults params,
|
||||
model: editor
|
||||
config: atom.config
|
||||
contentFrameWidth: 500
|
||||
lineTopIndex: lineTopIndex
|
||||
presenter = new TextEditorPresenter(params)
|
||||
presenter.setLinesYardstick(new FakeLinesYardstick(editor, presenter))
|
||||
presenter.setLinesYardstick(new FakeLinesYardstick(editor, lineTopIndex))
|
||||
presenter
|
||||
|
||||
buildPresenter = (params={}) ->
|
||||
@ -164,6 +181,99 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
expect(stateFn(presenter).tiles[12]).toBeUndefined()
|
||||
|
||||
describe "when there are block decorations", ->
|
||||
it "computes each tile's height and scrollTop based on block decorations' height", ->
|
||||
presenter = buildPresenter(explicitHeight: 120, scrollTop: 0, lineHeight: 10, tileSize: 2)
|
||||
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(3)
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(5)
|
||||
blockDecoration4 = addBlockDecorationAfterScreenRow(5)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 1)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 30)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration3, 0, 40)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration4, 0, 50)
|
||||
|
||||
expect(stateFn(presenter).tiles[0].height).toBe(2 * 10 + 1)
|
||||
expect(stateFn(presenter).tiles[0].top).toBe(0 * 10)
|
||||
expect(stateFn(presenter).tiles[2].height).toBe(2 * 10 + 30)
|
||||
expect(stateFn(presenter).tiles[2].top).toBe(2 * 10 + 1)
|
||||
expect(stateFn(presenter).tiles[4].height).toBe(2 * 10 + 40 + 50)
|
||||
expect(stateFn(presenter).tiles[4].top).toBe(4 * 10 + 1 + 30)
|
||||
expect(stateFn(presenter).tiles[6].height).toBe(2 * 10)
|
||||
expect(stateFn(presenter).tiles[6].top).toBe(6 * 10 + 1 + 30 + 40 + 50)
|
||||
expect(stateFn(presenter).tiles[8]).toBeUndefined()
|
||||
|
||||
presenter.setScrollTop(21)
|
||||
|
||||
expect(stateFn(presenter).tiles[0]).toBeUndefined()
|
||||
expect(stateFn(presenter).tiles[2].height).toBe(2 * 10 + 30)
|
||||
expect(stateFn(presenter).tiles[2].top).toBe(2 * 10 + 1 - 21)
|
||||
expect(stateFn(presenter).tiles[4].height).toBe(2 * 10 + 40 + 50)
|
||||
expect(stateFn(presenter).tiles[4].top).toBe(4 * 10 + 1 + 30 - 21)
|
||||
expect(stateFn(presenter).tiles[6].height).toBe(2 * 10)
|
||||
expect(stateFn(presenter).tiles[6].top).toBe(6 * 10 + 1 + 30 + 40 + 50 - 21)
|
||||
expect(stateFn(presenter).tiles[8]).toBeUndefined()
|
||||
|
||||
blockDecoration3.getMarker().setHeadScreenPosition([6, 0])
|
||||
|
||||
expect(stateFn(presenter).tiles[0]).toBeUndefined()
|
||||
expect(stateFn(presenter).tiles[2].height).toBe(2 * 10 + 30)
|
||||
expect(stateFn(presenter).tiles[2].top).toBe(2 * 10 + 1 - 21)
|
||||
expect(stateFn(presenter).tiles[4].height).toBe(2 * 10 + 50)
|
||||
expect(stateFn(presenter).tiles[4].top).toBe(4 * 10 + 1 + 30 - 21)
|
||||
expect(stateFn(presenter).tiles[6].height).toBe(2 * 10 + 40)
|
||||
expect(stateFn(presenter).tiles[6].top).toBe(6 * 10 + 1 + 30 + 50 - 21)
|
||||
expect(stateFn(presenter).tiles[8]).toBeUndefined()
|
||||
|
||||
it "works correctly when soft wrapping is enabled", ->
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(4)
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(8)
|
||||
|
||||
presenter = buildPresenter(explicitHeight: 330, lineHeight: 10, tileSize: 2, baseCharacterWidth: 5)
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 20)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration3, 0, 30)
|
||||
|
||||
expect(stateFn(presenter).tiles[0].top).toBe(0 * 10)
|
||||
expect(stateFn(presenter).tiles[2].top).toBe(2 * 10 + 10)
|
||||
expect(stateFn(presenter).tiles[4].top).toBe(4 * 10 + 10)
|
||||
expect(stateFn(presenter).tiles[6].top).toBe(6 * 10 + 10 + 20)
|
||||
expect(stateFn(presenter).tiles[8].top).toBe(8 * 10 + 10 + 20)
|
||||
expect(stateFn(presenter).tiles[10].top).toBe(10 * 10 + 10 + 20 + 30)
|
||||
expect(stateFn(presenter).tiles[12].top).toBe(12 * 10 + 10 + 20 + 30)
|
||||
|
||||
editor.setSoftWrapped(true)
|
||||
presenter.setContentFrameWidth(5 * 25)
|
||||
|
||||
expect(stateFn(presenter).tiles[0].top).toBe(0 * 10)
|
||||
expect(stateFn(presenter).tiles[2].top).toBe(2 * 10 + 10)
|
||||
expect(stateFn(presenter).tiles[4].top).toBe(4 * 10 + 10)
|
||||
expect(stateFn(presenter).tiles[6].top).toBe(6 * 10 + 10)
|
||||
expect(stateFn(presenter).tiles[8].top).toBe(8 * 10 + 10)
|
||||
expect(stateFn(presenter).tiles[10].top).toBe(10 * 10 + 10)
|
||||
expect(stateFn(presenter).tiles[12].top).toBe(12 * 10 + 10 + 20)
|
||||
expect(stateFn(presenter).tiles[14].top).toBe(14 * 10 + 10 + 20)
|
||||
expect(stateFn(presenter).tiles[16].top).toBe(16 * 10 + 10 + 20)
|
||||
expect(stateFn(presenter).tiles[18].top).toBe(18 * 10 + 10 + 20)
|
||||
expect(stateFn(presenter).tiles[20].top).toBe(20 * 10 + 10 + 20)
|
||||
expect(stateFn(presenter).tiles[22].top).toBe(22 * 10 + 10 + 20 + 30)
|
||||
expect(stateFn(presenter).tiles[24].top).toBe(24 * 10 + 10 + 20 + 30)
|
||||
expect(stateFn(presenter).tiles[26].top).toBe(26 * 10 + 10 + 20 + 30)
|
||||
expect(stateFn(presenter).tiles[28].top).toBe(28 * 10 + 10 + 20 + 30)
|
||||
|
||||
editor.setSoftWrapped(false)
|
||||
|
||||
expect(stateFn(presenter).tiles[0].top).toBe(0 * 10)
|
||||
expect(stateFn(presenter).tiles[2].top).toBe(2 * 10 + 10)
|
||||
expect(stateFn(presenter).tiles[4].top).toBe(4 * 10 + 10)
|
||||
expect(stateFn(presenter).tiles[6].top).toBe(6 * 10 + 10 + 20)
|
||||
expect(stateFn(presenter).tiles[8].top).toBe(8 * 10 + 10 + 20)
|
||||
expect(stateFn(presenter).tiles[10].top).toBe(10 * 10 + 10 + 20 + 30)
|
||||
expect(stateFn(presenter).tiles[12].top).toBe(12 * 10 + 10 + 20 + 30)
|
||||
|
||||
it "includes state for all tiles if no external ::explicitHeight is assigned", ->
|
||||
presenter = buildPresenter(explicitHeight: null, tileSize: 2)
|
||||
expect(stateFn(presenter).tiles[0]).toBeDefined()
|
||||
@ -363,7 +473,7 @@ describe "TextEditorPresenter", ->
|
||||
expect(getState(presenter).horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1
|
||||
expectStateUpdate presenter, ->
|
||||
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'support.function.js'], 'p', 20)
|
||||
presenter.characterWidthsChanged()
|
||||
presenter.measurementsChanged()
|
||||
expect(getState(presenter).horizontalScrollbar.scrollWidth).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide
|
||||
|
||||
it "updates when ::softWrapped changes on the editor", ->
|
||||
@ -482,6 +592,32 @@ describe "TextEditorPresenter", ->
|
||||
presenter = buildPresenter(scrollTop: 0, lineHeight: 10, explicitHeight: 500)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe 500
|
||||
|
||||
it "updates when new block decorations are measured, changed or destroyed", ->
|
||||
presenter = buildPresenter(scrollTop: 0, lineHeight: 10)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10
|
||||
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(3)
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(7)
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 35.8)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 50.3)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration3, 0, 95.2)
|
||||
|
||||
linesHeight = editor.getScreenLineCount() * 10
|
||||
blockDecorationsHeight = Math.round(35.8 + 50.3 + 95.2)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe(linesHeight + blockDecorationsHeight)
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 100.3)
|
||||
|
||||
blockDecorationsHeight = Math.round(35.8 + 100.3 + 95.2)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe(linesHeight + blockDecorationsHeight)
|
||||
|
||||
waitsForStateToUpdate presenter, -> blockDecoration3.destroy()
|
||||
runs ->
|
||||
blockDecorationsHeight = Math.round(35.8 + 100.3)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe(linesHeight + blockDecorationsHeight)
|
||||
|
||||
it "updates when the ::lineHeight changes", ->
|
||||
presenter = buildPresenter(scrollTop: 0, lineHeight: 10)
|
||||
expectStateUpdate presenter, -> presenter.setLineHeight(20)
|
||||
@ -608,7 +744,7 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
expectStateUpdate presenter, ->
|
||||
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'storage.type.var.js'], 'r', 20)
|
||||
presenter.characterWidthsChanged()
|
||||
presenter.measurementsChanged()
|
||||
expect(getState(presenter).hiddenInput.width).toBe 20
|
||||
|
||||
it "is 2px at the end of lines", ->
|
||||
@ -630,6 +766,32 @@ describe "TextEditorPresenter", ->
|
||||
expect(getState(presenter).content.maxHeight).toBe(50)
|
||||
|
||||
describe ".scrollHeight", ->
|
||||
it "updates when new block decorations are measured, changed or destroyed", ->
|
||||
presenter = buildPresenter(scrollTop: 0, lineHeight: 10)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10
|
||||
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(3)
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(7)
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 35.8)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 50.3)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration3, 0, 95.2)
|
||||
|
||||
linesHeight = editor.getScreenLineCount() * 10
|
||||
blockDecorationsHeight = Math.round(35.8 + 50.3 + 95.2)
|
||||
expect(getState(presenter).content.scrollHeight).toBe(linesHeight + blockDecorationsHeight)
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 100.3)
|
||||
|
||||
blockDecorationsHeight = Math.round(35.8 + 100.3 + 95.2)
|
||||
expect(getState(presenter).content.scrollHeight).toBe(linesHeight + blockDecorationsHeight)
|
||||
|
||||
waitsForStateToUpdate presenter, -> blockDecoration3.destroy()
|
||||
runs ->
|
||||
blockDecorationsHeight = Math.round(35.8 + 100.3)
|
||||
expect(getState(presenter).content.scrollHeight).toBe(linesHeight + blockDecorationsHeight)
|
||||
|
||||
it "is initialized based on the lineHeight, the number of lines, and the height", ->
|
||||
presenter = buildPresenter(scrollTop: 0, lineHeight: 10)
|
||||
expect(getState(presenter).content.scrollHeight).toBe editor.getScreenLineCount() * 10
|
||||
@ -705,7 +867,7 @@ describe "TextEditorPresenter", ->
|
||||
expect(getState(presenter).content.scrollWidth).toBe 10 * maxLineLength + 1
|
||||
expectStateUpdate presenter, ->
|
||||
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'support.function.js'], 'p', 20)
|
||||
presenter.characterWidthsChanged()
|
||||
presenter.measurementsChanged()
|
||||
expect(getState(presenter).content.scrollWidth).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide
|
||||
|
||||
it "updates when ::softWrapped changes on the editor", ->
|
||||
@ -811,6 +973,9 @@ describe "TextEditorPresenter", ->
|
||||
editor.insertNewline()
|
||||
expect(getState(presenter).content.scrollTop).toBe(10)
|
||||
|
||||
editor.insertNewline()
|
||||
expect(getState(presenter).content.scrollTop).toBe(20)
|
||||
|
||||
it "never exceeds the computed scroll height minus the computed client height", ->
|
||||
didChangeScrollTopSpy = jasmine.createSpy()
|
||||
presenter = buildPresenter(scrollTop: 10, lineHeight: 10, explicitHeight: 50, horizontalScrollbarHeight: 10)
|
||||
@ -1165,6 +1330,142 @@ describe "TextEditorPresenter", ->
|
||||
expect(lineStateForScreenRow(presenter, 0).endOfLineInvisibles).toEqual [atom.config.get('editor.invisibles.eol')]
|
||||
expect(lineStateForScreenRow(presenter, 1).endOfLineInvisibles).toEqual [atom.config.get('editor.invisibles.cr'), atom.config.get('editor.invisibles.eol')]
|
||||
|
||||
describe ".blockDecorations", ->
|
||||
it "contains all block decorations that are present before/after a line, both initially and when decorations change", ->
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
|
||||
presenter = buildPresenter()
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(3)
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(7)
|
||||
blockDecoration4 = addBlockDecorationAfterScreenRow(7)
|
||||
|
||||
waitsForStateToUpdate presenter
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual([blockDecoration1])
|
||||
expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 1).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 1).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 2).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 2).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 3).precedingBlockDecorations).toEqual([blockDecoration2])
|
||||
expect(lineStateForScreenRow(presenter, 3).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 5).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 5).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 6).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 6).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 7).precedingBlockDecorations).toEqual([blockDecoration3])
|
||||
expect(lineStateForScreenRow(presenter, 7).followingBlockDecorations).toEqual([blockDecoration4])
|
||||
expect(lineStateForScreenRow(presenter, 8).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 8).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 9).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 9).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 10).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 10).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 11).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 11).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 12).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 12).followingBlockDecorations).toEqual([])
|
||||
|
||||
waitsForStateToUpdate presenter, ->
|
||||
blockDecoration1.getMarker().setHeadBufferPosition([1, 0])
|
||||
blockDecoration2.getMarker().setHeadBufferPosition([9, 0])
|
||||
blockDecoration3.getMarker().setHeadBufferPosition([9, 0])
|
||||
blockDecoration4.getMarker().setHeadBufferPosition([8, 0])
|
||||
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 1).precedingBlockDecorations).toEqual([blockDecoration1])
|
||||
expect(lineStateForScreenRow(presenter, 1).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 2).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 2).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 3).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 3).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 5).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 5).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 6).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 6).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 7).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 7).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 8).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 8).followingBlockDecorations).toEqual([blockDecoration4])
|
||||
expect(lineStateForScreenRow(presenter, 9).precedingBlockDecorations).toEqual([blockDecoration2, blockDecoration3])
|
||||
expect(lineStateForScreenRow(presenter, 9).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 10).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 10).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 11).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 11).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 12).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 12).followingBlockDecorations).toEqual([])
|
||||
|
||||
waitsForStateToUpdate presenter, ->
|
||||
blockDecoration4.destroy()
|
||||
blockDecoration3.destroy()
|
||||
blockDecoration1.getMarker().setHeadBufferPosition([0, 0])
|
||||
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual([blockDecoration1])
|
||||
expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 1).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 1).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 2).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 2).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 3).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 3).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 5).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 5).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 6).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 6).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 7).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 7).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 8).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 8).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 9).precedingBlockDecorations).toEqual([blockDecoration2])
|
||||
expect(lineStateForScreenRow(presenter, 9).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 10).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 10).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 11).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 11).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 12).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 12).followingBlockDecorations).toEqual([])
|
||||
|
||||
waitsForStateToUpdate presenter, ->
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
editor.insertNewline()
|
||||
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 1).precedingBlockDecorations).toEqual([blockDecoration1])
|
||||
expect(lineStateForScreenRow(presenter, 1).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 2).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 2).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 3).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 3).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 5).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 5).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 6).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 6).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 7).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 7).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 8).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 8).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 9).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 9).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 10).precedingBlockDecorations).toEqual([blockDecoration2])
|
||||
expect(lineStateForScreenRow(presenter, 10).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 11).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 11).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 12).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 12).followingBlockDecorations).toEqual([])
|
||||
|
||||
describe ".decorationClasses", ->
|
||||
it "adds decoration classes to the relevant line state objects, both initially and when decorations change", ->
|
||||
marker1 = editor.addMarkerLayer(maintainHistory: true).markBufferRange([[4, 0], [6, 2]], invalidate: 'touch')
|
||||
@ -1360,6 +1661,45 @@ describe "TextEditorPresenter", ->
|
||||
presenter.setHorizontalScrollbarHeight(10)
|
||||
expect(getState(presenter).content.cursors).not.toEqual({})
|
||||
|
||||
it "updates when block decorations change", ->
|
||||
editor.setSelectedBufferRanges([
|
||||
[[1, 2], [1, 2]],
|
||||
[[2, 4], [2, 4]],
|
||||
[[3, 4], [3, 5]]
|
||||
[[5, 12], [5, 12]],
|
||||
[[8, 4], [8, 4]]
|
||||
])
|
||||
presenter = buildPresenter(explicitHeight: 80, scrollTop: 0)
|
||||
|
||||
expect(stateForCursor(presenter, 0)).toEqual {top: 10, left: 2 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 1)).toEqual {top: 20, left: 4 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 2)).toBeUndefined()
|
||||
expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 10, left: 12 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 4)).toEqual {top: 8 * 10, left: 4 * 10, width: 10, height: 10}
|
||||
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(1)
|
||||
|
||||
waitsForStateToUpdate presenter, ->
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 30)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 10)
|
||||
|
||||
runs ->
|
||||
expect(stateForCursor(presenter, 0)).toEqual {top: 50, left: 2 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 1)).toEqual {top: 60, left: 4 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 2)).toBeUndefined()
|
||||
expect(stateForCursor(presenter, 3)).toBeUndefined()
|
||||
expect(stateForCursor(presenter, 4)).toBeUndefined()
|
||||
|
||||
waitsForStateToUpdate presenter, ->
|
||||
blockDecoration2.destroy()
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
editor.insertNewline()
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
|
||||
runs ->
|
||||
expect(stateForCursor(presenter, 0)).toEqual {top: 0, left: 0, width: 10, height: 10}
|
||||
|
||||
it "updates when ::scrollTop changes", ->
|
||||
editor.setSelectedBufferRanges([
|
||||
[[1, 2], [1, 2]],
|
||||
@ -1434,12 +1774,12 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
expectStateUpdate presenter, ->
|
||||
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'storage.type.var.js'], 'v', 20)
|
||||
presenter.characterWidthsChanged()
|
||||
presenter.measurementsChanged()
|
||||
expect(stateForCursor(presenter, 0)).toEqual {top: 1 * 10, left: (3 * 10) + 20, width: 10, height: 10}
|
||||
|
||||
expectStateUpdate presenter, ->
|
||||
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'storage.type.var.js'], 'r', 20)
|
||||
presenter.characterWidthsChanged()
|
||||
presenter.measurementsChanged()
|
||||
expect(stateForCursor(presenter, 0)).toEqual {top: 1 * 10, left: (3 * 10) + 20, width: 20, height: 10}
|
||||
|
||||
it "updates when cursors are added, moved, hidden, shown, or destroyed", ->
|
||||
@ -1785,7 +2125,7 @@ describe "TextEditorPresenter", ->
|
||||
}
|
||||
expectStateUpdate presenter, ->
|
||||
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'keyword.control.js'], 'i', 20)
|
||||
presenter.characterWidthsChanged()
|
||||
presenter.measurementsChanged()
|
||||
expectValues stateForSelectionInTile(presenter, 0, 2), {
|
||||
regions: [{top: 0, left: 4 * 10, width: 20 + 10, height: 10}]
|
||||
}
|
||||
@ -1897,6 +2237,223 @@ describe "TextEditorPresenter", ->
|
||||
flashCount: 2
|
||||
}
|
||||
|
||||
describe ".blockDecorations", ->
|
||||
stateForBlockDecoration = (presenter, decoration) ->
|
||||
getState(presenter).content.blockDecorations[decoration.id]
|
||||
|
||||
it "contains state for measured block decorations that are not visible when they are on ::mouseWheelScreenRow", ->
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
|
||||
presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0, stoppedScrollingDelay: 200)
|
||||
getState(presenter) # flush pending state
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 0)
|
||||
|
||||
presenter.setScrollTop(100)
|
||||
presenter.setMouseWheelScreenRow(0)
|
||||
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
|
||||
decoration: blockDecoration1
|
||||
screenRow: 0
|
||||
isVisible: true
|
||||
}
|
||||
|
||||
advanceClock(presenter.stoppedScrollingDelay)
|
||||
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
|
||||
|
||||
it "invalidates block decorations that intersect a change in the buffer", ->
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(9)
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(10)
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(11)
|
||||
presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0)
|
||||
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
|
||||
decoration: blockDecoration1
|
||||
screenRow: 9
|
||||
isVisible: false
|
||||
}
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration2), {
|
||||
decoration: blockDecoration2
|
||||
screenRow: 10
|
||||
isVisible: false
|
||||
}
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration3), {
|
||||
decoration: blockDecoration3
|
||||
screenRow: 11
|
||||
isVisible: false
|
||||
}
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 10)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration3, 0, 10)
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration2)).toBeUndefined()
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration3)).toBeUndefined()
|
||||
|
||||
editor.setSelectedScreenRange([[10, 0], [12, 0]])
|
||||
editor.delete()
|
||||
presenter.setScrollTop(0) # deleting the buffer causes the editor to autoscroll
|
||||
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration2), {
|
||||
decoration: blockDecoration2
|
||||
screenRow: 10
|
||||
isVisible: false
|
||||
}
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration3), {
|
||||
decoration: blockDecoration3
|
||||
screenRow: 10
|
||||
isVisible: false
|
||||
}
|
||||
|
||||
it "invalidates all block decorations when content frame width, window size or bounding client rect change", ->
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(11)
|
||||
presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0)
|
||||
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
|
||||
decoration: blockDecoration1
|
||||
screenRow: 11
|
||||
isVisible: false
|
||||
}
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10)
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
|
||||
|
||||
presenter.setBoundingClientRect({top: 0, left: 0, width: 50, height: 30})
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
|
||||
decoration: blockDecoration1
|
||||
screenRow: 11
|
||||
isVisible: false
|
||||
}
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20)
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
|
||||
|
||||
presenter.setContentFrameWidth(100)
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
|
||||
decoration: blockDecoration1
|
||||
screenRow: 11
|
||||
isVisible: false
|
||||
}
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20)
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
|
||||
|
||||
presenter.setWindowSize(100, 200)
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
|
||||
decoration: blockDecoration1
|
||||
screenRow: 11
|
||||
isVisible: false
|
||||
}
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20)
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
|
||||
|
||||
it "contains state for on-screen and unmeasured block decorations, both initially and when they are updated or destroyed", ->
|
||||
item = {}
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0, item)
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(4, item)
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(4, item)
|
||||
blockDecoration4 = addBlockDecorationBeforeScreenRow(10, item)
|
||||
presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0)
|
||||
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
|
||||
decoration: blockDecoration1
|
||||
screenRow: 0
|
||||
isVisible: true
|
||||
}
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration2), {
|
||||
decoration: blockDecoration2
|
||||
screenRow: 4
|
||||
isVisible: true
|
||||
}
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration3), {
|
||||
decoration: blockDecoration3
|
||||
screenRow: 4
|
||||
isVisible: true
|
||||
}
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration4), {
|
||||
decoration: blockDecoration4
|
||||
screenRow: 10
|
||||
isVisible: false
|
||||
}
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration4, 0, 20)
|
||||
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
|
||||
decoration: blockDecoration1
|
||||
screenRow: 0
|
||||
isVisible: true
|
||||
}
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration2), {
|
||||
decoration: blockDecoration2
|
||||
screenRow: 4
|
||||
isVisible: false
|
||||
}
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration3), {
|
||||
decoration: blockDecoration3
|
||||
screenRow: 4
|
||||
isVisible: false
|
||||
}
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration4)).toBeUndefined()
|
||||
|
||||
blockDecoration3.getMarker().setHeadScreenPosition([5, 0])
|
||||
presenter.setScrollTop(90)
|
||||
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration2), {
|
||||
decoration: blockDecoration2
|
||||
screenRow: 4
|
||||
isVisible: false
|
||||
}
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration3), {
|
||||
decoration: blockDecoration3
|
||||
screenRow: 5
|
||||
isVisible: false
|
||||
}
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration4), {
|
||||
decoration: blockDecoration4
|
||||
screenRow: 10
|
||||
isVisible: true
|
||||
}
|
||||
|
||||
presenter.invalidateBlockDecorationDimensions(blockDecoration1)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 10)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration3, 0, 10)
|
||||
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
|
||||
decoration: blockDecoration1
|
||||
screenRow: 0
|
||||
isVisible: false
|
||||
}
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration2)).toBeUndefined()
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration3)).toBeUndefined()
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration4), {
|
||||
decoration: blockDecoration4
|
||||
screenRow: 10
|
||||
isVisible: true
|
||||
}
|
||||
|
||||
blockDecoration1.destroy()
|
||||
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration2)).toBeUndefined()
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration3)).toBeUndefined()
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration4), {
|
||||
decoration: blockDecoration4
|
||||
screenRow: 10
|
||||
isVisible: true
|
||||
}
|
||||
|
||||
it "doesn't throw an error when setting the dimensions for a destroyed decoration", ->
|
||||
blockDecoration = addBlockDecorationBeforeScreenRow(0)
|
||||
presenter = buildPresenter()
|
||||
|
||||
blockDecoration.destroy()
|
||||
presenter.setBlockDecorationDimensions(blockDecoration, 30, 30)
|
||||
|
||||
expect(getState(presenter).content.blockDecorations).toEqual({})
|
||||
|
||||
describe ".overlays", ->
|
||||
[item] = []
|
||||
stateForOverlay = (presenter, decoration) ->
|
||||
@ -2396,6 +2953,76 @@ describe "TextEditorPresenter", ->
|
||||
expectValues lineNumberStateForScreenRow(presenter, 6), {screenRow: 6, bufferRow: 5, softWrapped: false}
|
||||
expectValues lineNumberStateForScreenRow(presenter, 7), {screenRow: 7, bufferRow: 6, softWrapped: false}
|
||||
|
||||
describe ".blockDecorationsHeight", ->
|
||||
it "adds the sum of all block decorations' heights to the relevant line number state objects, both initially and when decorations change", ->
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
|
||||
presenter = buildPresenter(tileSize: 2, explicitHeight: 300)
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(3)
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(3)
|
||||
blockDecoration4 = addBlockDecorationBeforeScreenRow(7)
|
||||
blockDecoration5 = addBlockDecorationAfterScreenRow(7)
|
||||
blockDecoration6 = addBlockDecorationAfterScreenRow(10)
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 20)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration3, 0, 30)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration4, 0, 35)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration4, 0, 40)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration5, 0, 50)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration6, 0, 60)
|
||||
|
||||
waitsForStateToUpdate presenter
|
||||
runs ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 0).blockDecorationsHeight).toBe(10)
|
||||
expect(lineNumberStateForScreenRow(presenter, 1).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 2).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).blockDecorationsHeight).toBe(20 + 30)
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).blockDecorationsHeight).toBe(40)
|
||||
expect(lineNumberStateForScreenRow(presenter, 8).blockDecorationsHeight).toBe(0) # 0 because we're at the start of a tile.
|
||||
expect(lineNumberStateForScreenRow(presenter, 9).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 10).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 11).blockDecorationsHeight).toBe(60)
|
||||
|
||||
waitsForStateToUpdate presenter, ->
|
||||
blockDecoration1.getMarker().setHeadBufferPosition([1, 0])
|
||||
blockDecoration2.getMarker().setHeadBufferPosition([5, 0])
|
||||
blockDecoration3.getMarker().setHeadBufferPosition([9, 0])
|
||||
|
||||
runs ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 0).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 1).blockDecorationsHeight).toBe(10)
|
||||
expect(lineNumberStateForScreenRow(presenter, 2).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).blockDecorationsHeight).toBe(20)
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).blockDecorationsHeight).toBe(40)
|
||||
expect(lineNumberStateForScreenRow(presenter, 8).blockDecorationsHeight).toBe(0) # 0 because we're at the start of a tile.
|
||||
expect(lineNumberStateForScreenRow(presenter, 9).blockDecorationsHeight).toBe(30)
|
||||
expect(lineNumberStateForScreenRow(presenter, 10).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 11).blockDecorationsHeight).toBe(60)
|
||||
|
||||
waitsForStateToUpdate presenter, ->
|
||||
blockDecoration1.destroy()
|
||||
blockDecoration3.destroy()
|
||||
|
||||
runs ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 0).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 1).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 2).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).blockDecorationsHeight).toBe(20)
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).blockDecorationsHeight).toBe(40)
|
||||
expect(lineNumberStateForScreenRow(presenter, 8).blockDecorationsHeight).toBe(0) # 0 because we're at the start of a tile.
|
||||
expect(lineNumberStateForScreenRow(presenter, 9).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 10).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 11).blockDecorationsHeight).toBe(60)
|
||||
|
||||
describe ".decorationClasses", ->
|
||||
it "adds decoration classes to the relevant line number state objects, both initially and when decorations change", ->
|
||||
marker1 = editor.addMarkerLayer(maintainHistory: true).markBufferRange([[4, 0], [6, 2]], invalidate: 'touch')
|
||||
@ -2625,6 +3252,56 @@ describe "TextEditorPresenter", ->
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration3.id].top).toBeDefined()
|
||||
|
||||
it "updates when block decorations are added, changed or removed", ->
|
||||
# block decoration before decoration1
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 3)
|
||||
# block decoration between decoration1 and decoration2
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(3)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 5)
|
||||
# block decoration between decoration2 and decoration3
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(10)
|
||||
blockDecoration4 = addBlockDecorationAfterScreenRow(10)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration3, 0, 7)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration4, 0, 11)
|
||||
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id].top).toBe lineHeight * marker1.getScreenRange().start.row + 3
|
||||
expect(decorationState[decoration1.id].height).toBe lineHeight * marker1.getScreenRange().getRowCount()
|
||||
expect(decorationState[decoration1.id].item).toBe decorationItem
|
||||
expect(decorationState[decoration1.id].class).toBe 'test-class'
|
||||
expect(decorationState[decoration2.id].top).toBe lineHeight * marker2.getScreenRange().start.row + 3 + 5
|
||||
expect(decorationState[decoration2.id].height).toBe lineHeight * marker2.getScreenRange().getRowCount() + 7 + 11
|
||||
expect(decorationState[decoration2.id].item).toBe decorationItem
|
||||
expect(decorationState[decoration2.id].class).toBe 'test-class'
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
|
||||
presenter.setScrollTop(scrollTop + lineHeight * 5)
|
||||
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration2.id].top).toBe lineHeight * marker2.getScreenRange().start.row + 3 + 5
|
||||
expect(decorationState[decoration2.id].height).toBe lineHeight * marker2.getScreenRange().getRowCount() + 7 + 11
|
||||
expect(decorationState[decoration2.id].item).toBe decorationItem
|
||||
expect(decorationState[decoration2.id].class).toBe 'test-class'
|
||||
expect(decorationState[decoration3.id].top).toBe lineHeight * marker3.getScreenRange().start.row + 3 + 5 + 7 + 11
|
||||
expect(decorationState[decoration3.id].height).toBe lineHeight * marker3.getScreenRange().getRowCount()
|
||||
expect(decorationState[decoration3.id].item).toBe decorationItem
|
||||
expect(decorationState[decoration3.id].class).toBe 'test-class'
|
||||
|
||||
waitsForStateToUpdate presenter, -> blockDecoration1.destroy()
|
||||
runs ->
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration2.id].top).toBe lineHeight * marker2.getScreenRange().start.row + 5
|
||||
expect(decorationState[decoration2.id].height).toBe lineHeight * marker2.getScreenRange().getRowCount() + 7 + 11
|
||||
expect(decorationState[decoration2.id].item).toBe decorationItem
|
||||
expect(decorationState[decoration2.id].class).toBe 'test-class'
|
||||
expect(decorationState[decoration3.id].top).toBe lineHeight * marker3.getScreenRange().start.row + 5 + 7 + 11
|
||||
expect(decorationState[decoration3.id].height).toBe lineHeight * marker3.getScreenRange().getRowCount()
|
||||
expect(decorationState[decoration3.id].item).toBe decorationItem
|
||||
expect(decorationState[decoration3.id].class).toBe 'test-class'
|
||||
|
||||
it "updates when ::scrollTop changes", ->
|
||||
# This update will scroll decoration1 out of view, and decoration3 into view.
|
||||
expectStateUpdate presenter, -> presenter.setScrollTop(scrollTop + lineHeight * 5)
|
||||
@ -2648,6 +3325,7 @@ describe "TextEditorPresenter", ->
|
||||
expectStateUpdate presenter, -> presenter.setLineHeight(Math.ceil(1.0 * explicitHeight / marker3.getBufferRange().end.row))
|
||||
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
|
||||
expect(decorationState[decoration1.id].top).toBeDefined()
|
||||
expect(decorationState[decoration2.id].top).toBeDefined()
|
||||
expect(decorationState[decoration3.id].top).toBeDefined()
|
||||
@ -2830,6 +3508,32 @@ describe "TextEditorPresenter", ->
|
||||
customGutter.destroy()
|
||||
|
||||
describe ".scrollHeight", ->
|
||||
it "updates when new block decorations are measured, changed or destroyed", ->
|
||||
presenter = buildPresenter(scrollTop: 0, lineHeight: 10)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10
|
||||
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(3)
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(7)
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 35.8)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 50.3)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration3, 0, 95.2)
|
||||
|
||||
linesHeight = editor.getScreenLineCount() * 10
|
||||
blockDecorationsHeight = Math.round(35.8 + 50.3 + 95.2)
|
||||
expect(getStylesForGutterWithName(presenter, 'line-number').scrollHeight).toBe(linesHeight + blockDecorationsHeight)
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 100.3)
|
||||
|
||||
blockDecorationsHeight = Math.round(35.8 + 100.3 + 95.2)
|
||||
expect(getStylesForGutterWithName(presenter, 'line-number').scrollHeight).toBe(linesHeight + blockDecorationsHeight)
|
||||
|
||||
waitsForStateToUpdate presenter, -> blockDecoration3.destroy()
|
||||
runs ->
|
||||
blockDecorationsHeight = Math.round(35.8 + 100.3)
|
||||
expect(getStylesForGutterWithName(presenter, 'line-number').scrollHeight).toBe(linesHeight + blockDecorationsHeight)
|
||||
|
||||
it "is initialized based on ::lineHeight, the number of lines, and ::explicitHeight", ->
|
||||
presenter = buildPresenter()
|
||||
expect(getStylesForGutterWithName(presenter, 'line-number').scrollHeight).toBe editor.getScreenLineCount() * 10
|
||||
|
@ -42,6 +42,10 @@ exports.getCachePath = function (sourceCode) {
|
||||
exports.compile = function (sourceCode, filePath) {
|
||||
if (!babel) {
|
||||
babel = require('babel-core')
|
||||
var Logger = require('babel-core/lib/transformation/file/logger')
|
||||
var noop = function () {}
|
||||
Logger.prototype.debug = noop
|
||||
Logger.prototype.verbose = noop
|
||||
}
|
||||
|
||||
var options = {filename: filePath}
|
||||
|
72
src/block-decorations-component.coffee
Normal file
72
src/block-decorations-component.coffee
Normal file
@ -0,0 +1,72 @@
|
||||
cloneObject = (object) ->
|
||||
clone = {}
|
||||
clone[key] = value for key, value of object
|
||||
clone
|
||||
|
||||
module.exports =
|
||||
class BlockDecorationsComponent
|
||||
constructor: (@container, @views, @presenter, @domElementPool) ->
|
||||
@newState = null
|
||||
@oldState = null
|
||||
@blockDecorationNodesById = {}
|
||||
@domNode = @domElementPool.buildElement("content")
|
||||
@domNode.setAttribute("select", ".atom--invisible-block-decoration")
|
||||
@domNode.style.visibility = "hidden"
|
||||
|
||||
getDomNode: ->
|
||||
@domNode
|
||||
|
||||
updateSync: (state) ->
|
||||
@newState = state.content
|
||||
@oldState ?= {blockDecorations: {}, width: 0}
|
||||
|
||||
if @newState.width isnt @oldState.width
|
||||
@domNode.style.width = @newState.width + "px"
|
||||
@oldState.width = @newState.width
|
||||
|
||||
for id, blockDecorationState of @oldState.blockDecorations
|
||||
unless @newState.blockDecorations.hasOwnProperty(id)
|
||||
@blockDecorationNodesById[id].remove()
|
||||
delete @blockDecorationNodesById[id]
|
||||
delete @oldState.blockDecorations[id]
|
||||
|
||||
for id, blockDecorationState of @newState.blockDecorations
|
||||
if @oldState.blockDecorations.hasOwnProperty(id)
|
||||
@updateBlockDecorationNode(id)
|
||||
else
|
||||
@oldState.blockDecorations[id] = {}
|
||||
@createAndAppendBlockDecorationNode(id)
|
||||
|
||||
measureBlockDecorations: ->
|
||||
for decorationId, blockDecorationNode of @blockDecorationNodesById
|
||||
style = getComputedStyle(blockDecorationNode)
|
||||
decoration = @newState.blockDecorations[decorationId].decoration
|
||||
marginBottom = parseInt(style.marginBottom) ? 0
|
||||
marginTop = parseInt(style.marginTop) ? 0
|
||||
@presenter.setBlockDecorationDimensions(
|
||||
decoration,
|
||||
blockDecorationNode.offsetWidth,
|
||||
blockDecorationNode.offsetHeight + marginTop + marginBottom
|
||||
)
|
||||
|
||||
createAndAppendBlockDecorationNode: (id) ->
|
||||
blockDecorationState = @newState.blockDecorations[id]
|
||||
blockDecorationNode = @views.getView(blockDecorationState.decoration.getProperties().item)
|
||||
blockDecorationNode.id = "atom--block-decoration-#{id}"
|
||||
@container.appendChild(blockDecorationNode)
|
||||
@blockDecorationNodesById[id] = blockDecorationNode
|
||||
@updateBlockDecorationNode(id)
|
||||
|
||||
updateBlockDecorationNode: (id) ->
|
||||
newBlockDecorationState = @newState.blockDecorations[id]
|
||||
oldBlockDecorationState = @oldState.blockDecorations[id]
|
||||
blockDecorationNode = @blockDecorationNodesById[id]
|
||||
|
||||
if newBlockDecorationState.isVisible
|
||||
blockDecorationNode.classList.remove("atom--invisible-block-decoration")
|
||||
else
|
||||
blockDecorationNode.classList.add("atom--invisible-block-decoration")
|
||||
|
||||
if oldBlockDecorationState.screenRow isnt newBlockDecorationState.screenRow
|
||||
blockDecorationNode.dataset.screenRow = newBlockDecorationState.screenRow
|
||||
oldBlockDecorationState.screenRow = newBlockDecorationState.screenRow
|
@ -103,8 +103,6 @@ class ApplicationMenu
|
||||
downloadingUpdateItem.visible = false
|
||||
installUpdateItem.visible = false
|
||||
|
||||
return if @autoUpdateManager.isDisabled()
|
||||
|
||||
switch state
|
||||
when 'idle', 'error', 'no-update-available'
|
||||
checkForUpdateItem.visible = true
|
||||
@ -119,9 +117,10 @@ class ApplicationMenu
|
||||
#
|
||||
# Returns an Array of menu item Objects.
|
||||
getDefaultTemplate: ->
|
||||
template = [
|
||||
[
|
||||
label: "Atom"
|
||||
submenu: [
|
||||
{label: "Check for Update", metadata: {autoUpdate: true}}
|
||||
{label: 'Reload', accelerator: 'Command+R', click: => @focusedWindow()?.reload()}
|
||||
{label: 'Close Window', accelerator: 'Command+Shift+W', click: => @focusedWindow()?.close()}
|
||||
{label: 'Toggle Dev Tools', accelerator: 'Command+Alt+I', click: => @focusedWindow()?.toggleDevTools()}
|
||||
@ -129,10 +128,6 @@ class ApplicationMenu
|
||||
]
|
||||
]
|
||||
|
||||
# Add `Check for Update` button if autoUpdateManager is enabled.
|
||||
template[0].submenu.unshift({label: "Check for Update", metadata: {autoUpdate: true}}) unless @autoUpdateManager.isDisabled()
|
||||
template
|
||||
|
||||
focusedWindow: ->
|
||||
_.find global.atomApplication.windows, (atomWindow) -> atomWindow.isFocused()
|
||||
|
||||
|
@ -74,8 +74,7 @@ class AtomApplication
|
||||
@pidsToOpenWindows = {}
|
||||
@windows = []
|
||||
|
||||
disableAutoUpdate = require(path.join(@resourcePath, 'package.json'))._disableAutoUpdate ? false
|
||||
@autoUpdateManager = new AutoUpdateManager(@version, options.test, disableAutoUpdate)
|
||||
@autoUpdateManager = new AutoUpdateManager(@version, options.test, @resourcePath)
|
||||
@applicationMenu = new ApplicationMenu(@version, @autoUpdateManager)
|
||||
@atomProtocolHandler = new AtomProtocolHandler(@resourcePath, @safeMode)
|
||||
|
||||
@ -167,7 +166,7 @@ class AtomApplication
|
||||
safeMode: @focusedWindow()?.safeMode
|
||||
|
||||
@on 'application:quit', -> app.quit()
|
||||
@on 'application:new-window', -> @openPath(_.extend(windowDimensions: @focusedWindow()?.getDimensions(), getLoadSettings()))
|
||||
@on 'application:new-window', -> @openPath(getLoadSettings())
|
||||
@on 'application:new-file', -> (@focusedWindow() ? this).openPath()
|
||||
@on 'application:open', -> @promptForPathToOpen('all', getLoadSettings())
|
||||
@on 'application:open-file', -> @promptForPathToOpen('file', getLoadSettings())
|
||||
@ -229,7 +228,7 @@ class AtomApplication
|
||||
@openUrl({urlToOpen, @devMode, @safeMode})
|
||||
|
||||
app.on 'activate-with-no-open-windows', (event) =>
|
||||
event.preventDefault()
|
||||
event?.preventDefault()
|
||||
@emit('application:new-window')
|
||||
|
||||
# A request from the associated render process to open a new render process.
|
||||
@ -360,6 +359,24 @@ class AtomApplication
|
||||
focusedWindow: ->
|
||||
_.find @windows, (atomWindow) -> atomWindow.isFocused()
|
||||
|
||||
# Get the platform-specific window offset for new windows.
|
||||
getWindowOffsetForCurrentPlatform: ->
|
||||
offsetByPlatform =
|
||||
darwin: 22
|
||||
win32: 26
|
||||
offsetByPlatform[process.platform] ? 0
|
||||
|
||||
# Get the dimensions for opening a new window by cascading as appropriate to
|
||||
# the platform.
|
||||
getDimensionsForNewWindow: ->
|
||||
return if (@focusedWindow() ? @lastFocusedWindow)?.isMaximized()
|
||||
dimensions = (@focusedWindow() ? @lastFocusedWindow)?.getDimensions()
|
||||
offset = @getWindowOffsetForCurrentPlatform()
|
||||
if dimensions? and offset?
|
||||
dimensions.x += offset
|
||||
dimensions.y += offset
|
||||
dimensions
|
||||
|
||||
# Public: Opens a single path, in an existing window if possible.
|
||||
#
|
||||
# options -
|
||||
@ -370,7 +387,7 @@ class AtomApplication
|
||||
# :safeMode - Boolean to control the opened window's safe mode.
|
||||
# :profileStartup - Boolean to control creating a profile of the startup time.
|
||||
# :window - {AtomWindow} to open file paths in.
|
||||
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window}) ->
|
||||
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window} = {}) ->
|
||||
@openPaths({pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window})
|
||||
|
||||
# Public: Opens multiple paths, in existing windows if possible.
|
||||
@ -417,6 +434,7 @@ class AtomApplication
|
||||
|
||||
windowInitializationScript ?= require.resolve('../initialize-application-window')
|
||||
resourcePath ?= @resourcePath
|
||||
windowDimensions ?= @getDimensionsForNewWindow()
|
||||
openedWindow = new AtomWindow({locationsToOpen, windowInitializationScript, resourcePath, devMode, safeMode, windowDimensions, profileStartup})
|
||||
|
||||
if pidToKillWhenClosed?
|
||||
|
@ -212,6 +212,8 @@ class AtomWindow
|
||||
|
||||
isFocused: -> @browserWindow.isFocused()
|
||||
|
||||
isMaximized: -> @browserWindow.isMaximized()
|
||||
|
||||
isMinimized: -> @browserWindow.isMinimized()
|
||||
|
||||
isWebViewFocused: -> @browserWindow.isWebViewFocused()
|
||||
|
@ -1,5 +1,6 @@
|
||||
autoUpdater = null
|
||||
_ = require 'underscore-plus'
|
||||
Config = require '../config'
|
||||
{EventEmitter} = require 'events'
|
||||
path = require 'path'
|
||||
|
||||
@ -15,10 +16,13 @@ module.exports =
|
||||
class AutoUpdateManager
|
||||
_.extend @prototype, EventEmitter.prototype
|
||||
|
||||
constructor: (@version, @testMode, @disabled) ->
|
||||
constructor: (@version, @testMode, resourcePath) ->
|
||||
@state = IdleState
|
||||
@iconPath = path.resolve(__dirname, '..', '..', 'resources', 'atom.png')
|
||||
@feedUrl = "https://atom.io/api/updates?version=#{@version}"
|
||||
@config = new Config({configDirPath: process.env.ATOM_HOME, resourcePath, enablePersistence: true})
|
||||
@config.setSchema null, {type: 'object', properties: _.clone(require('../config-schema'))}
|
||||
@config.load()
|
||||
process.nextTick => @setupAutoUpdater()
|
||||
|
||||
setupAutoUpdater: ->
|
||||
@ -46,9 +50,13 @@ class AutoUpdateManager
|
||||
@setState(UpdateAvailableState)
|
||||
@emitUpdateAvailableEvent(@getWindows()...)
|
||||
|
||||
# Only check for updates periodically if enabled and running in release
|
||||
# version.
|
||||
@scheduleUpdateCheck() unless /\w{7}/.test(@version) or @disabled
|
||||
@config.onDidChange 'core.automaticallyUpdate', ({newValue}) =>
|
||||
if newValue
|
||||
@scheduleUpdateCheck()
|
||||
else
|
||||
@cancelScheduledUpdateCheck()
|
||||
|
||||
@scheduleUpdateCheck() if @config.get 'core.automaticallyUpdate'
|
||||
|
||||
switch process.platform
|
||||
when 'win32'
|
||||
@ -56,9 +64,6 @@ class AutoUpdateManager
|
||||
when 'linux'
|
||||
@setState(UnsupportedState)
|
||||
|
||||
isDisabled: ->
|
||||
@disabled
|
||||
|
||||
emitUpdateAvailableEvent: (windows...) ->
|
||||
return unless @releaseVersion?
|
||||
for atomWindow in windows
|
||||
@ -74,10 +79,18 @@ class AutoUpdateManager
|
||||
@state
|
||||
|
||||
scheduleUpdateCheck: ->
|
||||
checkForUpdates = => @check(hidePopups: true)
|
||||
fourHours = 1000 * 60 * 60 * 4
|
||||
setInterval(checkForUpdates, fourHours)
|
||||
checkForUpdates()
|
||||
# Only schedule update check periodically if running in release version and
|
||||
# and there is no existing scheduled update check.
|
||||
unless /\w{7}/.test(@version) or @checkForUpdatesIntervalID
|
||||
checkForUpdates = => @check(hidePopups: true)
|
||||
fourHours = 1000 * 60 * 60 * 4
|
||||
@checkForUpdatesIntervalID = setInterval(checkForUpdates, fourHours)
|
||||
checkForUpdates()
|
||||
|
||||
cancelScheduledUpdateCheck: ->
|
||||
if @checkForUpdatesIntervalID
|
||||
clearInterval(@checkForUpdatesIntervalID)
|
||||
@checkForUpdatesIntervalID = null
|
||||
|
||||
check: ({hidePopups}={}) ->
|
||||
unless hidePopups
|
||||
|
@ -104,6 +104,10 @@ module.exports =
|
||||
description: 'Automatically open an empty editor on startup.'
|
||||
type: 'boolean'
|
||||
default: true
|
||||
automaticallyUpdate:
|
||||
description: 'Automatically update Atom when a new release is available.'
|
||||
type: 'boolean'
|
||||
default: true
|
||||
|
||||
editor:
|
||||
type: 'object'
|
||||
|
@ -779,9 +779,14 @@ class Config
|
||||
loadUserConfig: ->
|
||||
return if @shouldNotAccessFileSystem()
|
||||
|
||||
unless fs.existsSync(@configFilePath)
|
||||
fs.makeTreeSync(path.dirname(@configFilePath))
|
||||
CSON.writeFileSync(@configFilePath, {})
|
||||
try
|
||||
unless fs.existsSync(@configFilePath)
|
||||
fs.makeTreeSync(path.dirname(@configFilePath))
|
||||
CSON.writeFileSync(@configFilePath, {})
|
||||
catch error
|
||||
@configFileHasErrors = true
|
||||
@notifyFailure("Failed to initialize `#{path.basename(@configFilePath)}`", error.stack)
|
||||
return
|
||||
|
||||
try
|
||||
unless @savePending
|
||||
@ -820,7 +825,7 @@ class Config
|
||||
@watchSubscription = null
|
||||
|
||||
notifyFailure: (errorMessage, detail) ->
|
||||
@notificationManager.addError(errorMessage, {detail, dismissable: true})
|
||||
@notificationManager?.addError(errorMessage, {detail, dismissable: true})
|
||||
|
||||
save: ->
|
||||
return if @shouldNotAccessFileSystem()
|
||||
|
@ -35,7 +35,6 @@ translateDecorationParamsOldToNew = (decorationParams) ->
|
||||
# the marker.
|
||||
module.exports =
|
||||
class Decoration
|
||||
|
||||
# Private: Check if the `decorationProperties.type` matches `type`
|
||||
#
|
||||
# * `decorationProperties` {Object} eg. `{type: 'line-number', class: 'my-new-class'}`
|
||||
@ -154,6 +153,13 @@ class Decoration
|
||||
@displayBuffer.scheduleUpdateDecorationsEvent()
|
||||
@emitter.emit 'did-change-properties', {oldProperties, newProperties}
|
||||
|
||||
###
|
||||
Section: Utility
|
||||
###
|
||||
|
||||
inspect: ->
|
||||
"<Decoration #{@id}>"
|
||||
|
||||
###
|
||||
Section: Private methods
|
||||
###
|
||||
|
@ -812,6 +812,7 @@ class DisplayBuffer extends Model
|
||||
decorationsState
|
||||
|
||||
decorateMarker: (marker, decorationParams) ->
|
||||
throw new Error("Cannot decorate a destroyed marker") if marker.isDestroyed()
|
||||
marker = @getMarkerLayer(marker.layer.id).getMarker(marker.id)
|
||||
decoration = new Decoration(marker, this, decorationParams)
|
||||
@decorationsByMarkerId[marker.id] ?= []
|
||||
|
@ -463,8 +463,12 @@ class GitRepository
|
||||
refreshStatus: ->
|
||||
@handlerPath ?= require.resolve('./repository-status-handler')
|
||||
|
||||
relativeProjectPaths = @project?.getPaths()
|
||||
.map (path) => @relativize(path)
|
||||
.filter (path) -> path.length > 0
|
||||
|
||||
@statusTask?.terminate()
|
||||
@statusTask = Task.once @handlerPath, @getPath(), ({statuses, upstream, branch, submodules}) =>
|
||||
@statusTask = Task.once @handlerPath, @getPath(), relativeProjectPaths, ({statuses, upstream, branch, submodules}) =>
|
||||
statusesUnchanged = _.isEqual(statuses, @statuses) and
|
||||
_.isEqual(upstream, @upstream) and
|
||||
_.isEqual(branch, @branch) and
|
||||
|
@ -96,12 +96,13 @@ class LineNumbersTileComponent
|
||||
screenRowForNode: (node) -> parseInt(node.dataset.screenRow)
|
||||
|
||||
buildLineNumberNode: (lineNumberState) ->
|
||||
{screenRow, bufferRow, softWrapped, top, decorationClasses, zIndex} = lineNumberState
|
||||
{screenRow, bufferRow, softWrapped, top, decorationClasses, zIndex, blockDecorationsHeight} = lineNumberState
|
||||
|
||||
className = @buildLineNumberClassName(lineNumberState)
|
||||
lineNumberNode = @domElementPool.buildElement("div", className)
|
||||
lineNumberNode.dataset.screenRow = screenRow
|
||||
lineNumberNode.dataset.bufferRow = bufferRow
|
||||
lineNumberNode.style.marginTop = blockDecorationsHeight + "px"
|
||||
|
||||
@setLineNumberInnerNodes(bufferRow, softWrapped, lineNumberNode)
|
||||
lineNumberNode
|
||||
@ -139,6 +140,10 @@ class LineNumbersTileComponent
|
||||
oldLineNumberState.screenRow = newLineNumberState.screenRow
|
||||
oldLineNumberState.bufferRow = newLineNumberState.bufferRow
|
||||
|
||||
unless oldLineNumberState.blockDecorationsHeight is newLineNumberState.blockDecorationsHeight
|
||||
node.style.marginTop = newLineNumberState.blockDecorationsHeight + "px"
|
||||
oldLineNumberState.blockDecorationsHeight = newLineNumberState.blockDecorationsHeight
|
||||
|
||||
buildLineNumberClassName: ({bufferRow, foldable, decorationClasses, softWrapped}) ->
|
||||
className = "line-number"
|
||||
className += " " + decorationClasses.join(' ') if decorationClasses?
|
||||
|
@ -20,6 +20,8 @@ class LinesTileComponent
|
||||
@screenRowsByLineId = {}
|
||||
@lineIdsByScreenRow = {}
|
||||
@textNodesByLineId = {}
|
||||
@insertionPointsBeforeLineById = {}
|
||||
@insertionPointsAfterLineById = {}
|
||||
@domNode = @domElementPool.buildElement("div")
|
||||
@domNode.style.position = "absolute"
|
||||
@domNode.style.display = "block"
|
||||
@ -80,6 +82,9 @@ class LinesTileComponent
|
||||
|
||||
removeLineNode: (id) ->
|
||||
@domElementPool.freeElementAndDescendants(@lineNodesByLineId[id])
|
||||
@removeBlockDecorationInsertionPointBeforeLine(id)
|
||||
@removeBlockDecorationInsertionPointAfterLine(id)
|
||||
|
||||
delete @lineNodesByLineId[id]
|
||||
delete @textNodesByLineId[id]
|
||||
delete @lineIdsByScreenRow[@screenRowsByLineId[id]]
|
||||
@ -116,6 +121,71 @@ class LinesTileComponent
|
||||
else
|
||||
@domNode.appendChild(lineNode)
|
||||
|
||||
@insertBlockDecorationInsertionPointBeforeLine(id)
|
||||
@insertBlockDecorationInsertionPointAfterLine(id)
|
||||
|
||||
removeBlockDecorationInsertionPointBeforeLine: (id) ->
|
||||
if insertionPoint = @insertionPointsBeforeLineById[id]
|
||||
@domElementPool.freeElementAndDescendants(insertionPoint)
|
||||
delete @insertionPointsBeforeLineById[id]
|
||||
|
||||
insertBlockDecorationInsertionPointBeforeLine: (id) ->
|
||||
{hasPrecedingBlockDecorations, screenRow} = @newTileState.lines[id]
|
||||
|
||||
if hasPrecedingBlockDecorations
|
||||
lineNode = @lineNodesByLineId[id]
|
||||
insertionPoint = @domElementPool.buildElement("content")
|
||||
@domNode.insertBefore(insertionPoint, lineNode)
|
||||
@insertionPointsBeforeLineById[id] = insertionPoint
|
||||
insertionPoint.dataset.screenRow = screenRow
|
||||
@updateBlockDecorationInsertionPointBeforeLine(id)
|
||||
|
||||
updateBlockDecorationInsertionPointBeforeLine: (id) ->
|
||||
oldLineState = @oldTileState.lines[id]
|
||||
newLineState = @newTileState.lines[id]
|
||||
insertionPoint = @insertionPointsBeforeLineById[id]
|
||||
return unless insertionPoint?
|
||||
|
||||
if newLineState.screenRow isnt oldLineState.screenRow
|
||||
insertionPoint.dataset.screenRow = newLineState.screenRow
|
||||
|
||||
precedingBlockDecorationsSelector = newLineState.precedingBlockDecorations.map((d) -> "#atom--block-decoration-#{d.id}").join(',')
|
||||
|
||||
if precedingBlockDecorationsSelector isnt oldLineState.precedingBlockDecorationsSelector
|
||||
insertionPoint.setAttribute("select", precedingBlockDecorationsSelector)
|
||||
oldLineState.precedingBlockDecorationsSelector = precedingBlockDecorationsSelector
|
||||
|
||||
removeBlockDecorationInsertionPointAfterLine: (id) ->
|
||||
if insertionPoint = @insertionPointsAfterLineById[id]
|
||||
@domElementPool.freeElementAndDescendants(insertionPoint)
|
||||
delete @insertionPointsAfterLineById[id]
|
||||
|
||||
insertBlockDecorationInsertionPointAfterLine: (id) ->
|
||||
{hasFollowingBlockDecorations, screenRow} = @newTileState.lines[id]
|
||||
|
||||
if hasFollowingBlockDecorations
|
||||
lineNode = @lineNodesByLineId[id]
|
||||
insertionPoint = @domElementPool.buildElement("content")
|
||||
@domNode.insertBefore(insertionPoint, lineNode.nextSibling)
|
||||
@insertionPointsAfterLineById[id] = insertionPoint
|
||||
insertionPoint.dataset.screenRow = screenRow
|
||||
@updateBlockDecorationInsertionPointAfterLine(id)
|
||||
|
||||
updateBlockDecorationInsertionPointAfterLine: (id) ->
|
||||
oldLineState = @oldTileState.lines[id]
|
||||
newLineState = @newTileState.lines[id]
|
||||
insertionPoint = @insertionPointsAfterLineById[id]
|
||||
return unless insertionPoint?
|
||||
|
||||
if newLineState.screenRow isnt oldLineState.screenRow
|
||||
insertionPoint.dataset.screenRow = newLineState.screenRow
|
||||
|
||||
followingBlockDecorationsSelector = newLineState.followingBlockDecorations.map((d) -> "#atom--block-decoration-#{d.id}").join(',')
|
||||
|
||||
if followingBlockDecorationsSelector isnt oldLineState.followingBlockDecorationsSelector
|
||||
insertionPoint.setAttribute("select", followingBlockDecorationsSelector)
|
||||
oldLineState.followingBlockDecorationsSelector = followingBlockDecorationsSelector
|
||||
|
||||
findNodeNextTo: (node) ->
|
||||
for nextNode, index in @domNode.children
|
||||
continue if index is 0 # skips highlights node
|
||||
@ -336,12 +406,28 @@ class LinesTileComponent
|
||||
|
||||
oldLineState.decorationClasses = newLineState.decorationClasses
|
||||
|
||||
if not oldLineState.hasPrecedingBlockDecorations and newLineState.hasPrecedingBlockDecorations
|
||||
@insertBlockDecorationInsertionPointBeforeLine(id)
|
||||
else if oldLineState.hasPrecedingBlockDecorations and not newLineState.hasPrecedingBlockDecorations
|
||||
@removeBlockDecorationInsertionPointBeforeLine(id)
|
||||
|
||||
if not oldLineState.hasFollowingBlockDecorations and newLineState.hasFollowingBlockDecorations
|
||||
@insertBlockDecorationInsertionPointAfterLine(id)
|
||||
else if oldLineState.hasFollowingBlockDecorations and not newLineState.hasFollowingBlockDecorations
|
||||
@removeBlockDecorationInsertionPointAfterLine(id)
|
||||
|
||||
if newLineState.screenRow isnt oldLineState.screenRow
|
||||
lineNode.dataset.screenRow = newLineState.screenRow
|
||||
oldLineState.screenRow = newLineState.screenRow
|
||||
@lineIdsByScreenRow[newLineState.screenRow] = id
|
||||
@screenRowsByLineId[id] = newLineState.screenRow
|
||||
|
||||
@updateBlockDecorationInsertionPointBeforeLine(id)
|
||||
@updateBlockDecorationInsertionPointAfterLine(id)
|
||||
|
||||
oldLineState.screenRow = newLineState.screenRow
|
||||
oldLineState.hasPrecedingBlockDecorations = newLineState.hasPrecedingBlockDecorations
|
||||
oldLineState.hasFollowingBlockDecorations = newLineState.hasFollowingBlockDecorations
|
||||
|
||||
lineNodeForScreenRow: (screenRow) ->
|
||||
@lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
|
||||
|
||||
|
@ -3,7 +3,7 @@ TokenIterator = require './token-iterator'
|
||||
|
||||
module.exports =
|
||||
class LinesYardstick
|
||||
constructor: (@model, @lineNodesProvider, grammarRegistry) ->
|
||||
constructor: (@model, @lineNodesProvider, @lineTopIndex, grammarRegistry) ->
|
||||
@tokenIterator = new TokenIterator({grammarRegistry})
|
||||
@rangeForMeasurement = document.createRange()
|
||||
@invalidateCache()
|
||||
@ -20,8 +20,8 @@ class LinesYardstick
|
||||
targetTop = pixelPosition.top
|
||||
targetLeft = pixelPosition.left
|
||||
defaultCharWidth = @model.getDefaultCharWidth()
|
||||
row = Math.floor(targetTop / @model.getLineHeightInPixels())
|
||||
targetLeft = 0 if row < 0
|
||||
row = @lineTopIndex.rowForPixelPosition(targetTop)
|
||||
targetLeft = 0 if targetTop < 0
|
||||
targetLeft = Infinity if row > @model.getLastScreenRow()
|
||||
row = Math.min(row, @model.getLastScreenRow())
|
||||
row = Math.max(0, row)
|
||||
@ -77,14 +77,11 @@ class LinesYardstick
|
||||
else
|
||||
Point(row, column)
|
||||
|
||||
pixelPositionForScreenPosition: (screenPosition, clip=true) ->
|
||||
screenPosition = Point.fromObject(screenPosition)
|
||||
screenPosition = @model.clipScreenPosition(screenPosition) if clip
|
||||
|
||||
pixelPositionForScreenPosition: (screenPosition) ->
|
||||
targetRow = screenPosition.row
|
||||
targetColumn = screenPosition.column
|
||||
|
||||
top = targetRow * @model.getLineHeightInPixels()
|
||||
top = @lineTopIndex.pixelPositionAfterBlocksForRow(targetRow)
|
||||
left = @leftPixelPositionForScreenPosition(targetRow, targetColumn)
|
||||
|
||||
{top, left}
|
||||
|
@ -199,7 +199,10 @@ class PackageManager
|
||||
# Returns the {Package} that was disabled or null if it isn't loaded.
|
||||
disablePackage: (name) ->
|
||||
pack = @loadPackage(name)
|
||||
pack?.disable()
|
||||
|
||||
unless @isPackageDisabled(name)
|
||||
pack?.disable()
|
||||
|
||||
pack
|
||||
|
||||
# Public: Is the package with the given name disabled?
|
||||
|
@ -731,30 +731,28 @@ class Pane extends Model
|
||||
message = "#{message} '#{itemPath}'" if itemPath
|
||||
@notificationManager.addWarning(message, options)
|
||||
|
||||
if error.code is 'EISDIR' or error.message?.endsWith?('is a directory')
|
||||
customMessage = @getMessageForErrorCode(error.code)
|
||||
if customMessage?
|
||||
addWarningWithPath("Unable to save file: #{customMessage}")
|
||||
else if error.code is 'EISDIR' or error.message?.endsWith?('is a directory')
|
||||
@notificationManager.addWarning("Unable to save file: #{error.message}")
|
||||
else if error.code is 'EACCES'
|
||||
addWarningWithPath('Unable to save file: Permission denied')
|
||||
else if error.code in ['EPERM', 'EBUSY', 'UNKNOWN', 'EEXIST', 'ELOOP', 'EAGAIN']
|
||||
addWarningWithPath('Unable to save file', detail: error.message)
|
||||
else if error.code is 'EROFS'
|
||||
addWarningWithPath('Unable to save file: Read-only file system')
|
||||
else if error.code is 'ENOSPC'
|
||||
addWarningWithPath('Unable to save file: No space left on device')
|
||||
else if error.code is 'ENXIO'
|
||||
addWarningWithPath('Unable to save file: No such device or address')
|
||||
else if error.code is 'ENOTSUP'
|
||||
addWarningWithPath('Unable to save file: Operation not supported on socket')
|
||||
else if error.code is 'EIO'
|
||||
addWarningWithPath('Unable to save file: I/O error writing file')
|
||||
else if error.code is 'EINTR'
|
||||
addWarningWithPath('Unable to save file: Interrupted system call')
|
||||
else if error.code is 'ECONNRESET'
|
||||
addWarningWithPath('Unable to save file: Connection reset')
|
||||
else if error.code is 'ESPIPE'
|
||||
addWarningWithPath('Unable to save file: Invalid seek')
|
||||
else if errorMatch = /ENOTDIR, not a directory '([^']+)'/.exec(error.message)
|
||||
fileName = errorMatch[1]
|
||||
@notificationManager.addWarning("Unable to save file: A directory in the path '#{fileName}' could not be written to")
|
||||
else
|
||||
throw error
|
||||
|
||||
getMessageForErrorCode: (errorCode) ->
|
||||
switch errorCode
|
||||
when 'EACCES' then 'Permission denied'
|
||||
when 'ECONNRESET' then 'Connection reset'
|
||||
when 'EINTR' then 'Interrupted system call'
|
||||
when 'EIO' then 'I/O error writing file'
|
||||
when 'ENOSPC' then 'No space left on device'
|
||||
when 'ENOTSUP' then 'Operation not supported on socket'
|
||||
when 'ENXIO' then 'No such device or address'
|
||||
when 'EROFS' then 'Read-only file system'
|
||||
when 'ESPIPE' then 'Invalid seek'
|
||||
when 'ETIMEDOUT' then 'Connection timed out'
|
||||
|
@ -288,7 +288,7 @@ class Project extends Model
|
||||
'atom.repository-provider',
|
||||
'^0.1.0',
|
||||
(provider) =>
|
||||
@repositoryProviders.push(provider)
|
||||
@repositoryProviders.unshift(provider)
|
||||
@setPaths(@getPaths()) if null in @repositories
|
||||
new Disposable =>
|
||||
@repositoryProviders.splice(@repositoryProviders.indexOf(provider), 1)
|
||||
|
@ -169,7 +169,8 @@ module.exports = ({commandRegistry, commandInstaller, config}) ->
|
||||
'editor:fold-at-indent-level-8': -> @foldAllAtIndentLevel(7)
|
||||
'editor:fold-at-indent-level-9': -> @foldAllAtIndentLevel(8)
|
||||
'editor:log-cursor-scope': -> @logCursorScope()
|
||||
'editor:copy-path': -> @copyPathToClipboard()
|
||||
'editor:copy-path': -> @copyPathToClipboard(false)
|
||||
'editor:copy-project-path': -> @copyPathToClipboard(true)
|
||||
'editor:toggle-indent-guide': -> config.set('editor.showIndentGuide', not config.get('editor.showIndentGuide'))
|
||||
'editor:toggle-line-numbers': -> config.set('editor.showLineNumbers', not config.get('editor.showLineNumbers'))
|
||||
'editor:scroll-to-cursor': -> @scrollToCursorPosition()
|
||||
|
@ -1,7 +1,7 @@
|
||||
Git = require 'git-utils'
|
||||
path = require 'path'
|
||||
|
||||
module.exports = (repoPath) ->
|
||||
module.exports = (repoPath, paths = []) ->
|
||||
repo = Git.open(repoPath)
|
||||
|
||||
upstream = {}
|
||||
@ -12,7 +12,8 @@ module.exports = (repoPath) ->
|
||||
if repo?
|
||||
# Statuses in main repo
|
||||
workingDirectoryPath = repo.getWorkingDirectory()
|
||||
for filePath, status of repo.getStatus()
|
||||
repoStatus = (if paths.length > 0 then repo.getStatusForPaths(paths) else repo.getStatus())
|
||||
for filePath, status of repoStatus
|
||||
statuses[filePath] = status
|
||||
|
||||
# Statuses in submodules
|
||||
|
@ -13,6 +13,8 @@ ScrollbarCornerComponent = require './scrollbar-corner-component'
|
||||
OverlayManager = require './overlay-manager'
|
||||
DOMElementPool = require './dom-element-pool'
|
||||
LinesYardstick = require './lines-yardstick'
|
||||
BlockDecorationsComponent = require './block-decorations-component'
|
||||
LineTopIndex = require 'line-top-index'
|
||||
|
||||
module.exports =
|
||||
class TextEditorComponent
|
||||
@ -48,6 +50,9 @@ class TextEditorComponent
|
||||
@observeConfig()
|
||||
@setScrollSensitivity(@config.get('editor.scrollSensitivity'))
|
||||
|
||||
lineTopIndex = new LineTopIndex({
|
||||
defaultLineHeight: @editor.getLineHeightInPixels()
|
||||
})
|
||||
@presenter = new TextEditorPresenter
|
||||
model: @editor
|
||||
tileSize: tileSize
|
||||
@ -55,11 +60,11 @@ class TextEditorComponent
|
||||
cursorBlinkResumeDelay: @cursorBlinkResumeDelay
|
||||
stoppedScrollingDelay: 200
|
||||
config: @config
|
||||
lineTopIndex: lineTopIndex
|
||||
|
||||
@presenter.onDidUpdateState(@requestUpdate)
|
||||
|
||||
@domElementPool = new DOMElementPool
|
||||
|
||||
@domNode = document.createElement('div')
|
||||
if @useShadowDOM
|
||||
@domNode.classList.add('editor-contents--private')
|
||||
@ -68,6 +73,7 @@ class TextEditorComponent
|
||||
insertionPoint.setAttribute('select', 'atom-overlay')
|
||||
@domNode.appendChild(insertionPoint)
|
||||
@overlayManager = new OverlayManager(@presenter, @hostElement, @views)
|
||||
@blockDecorationsComponent = new BlockDecorationsComponent(@hostElement, @views, @presenter, @domElementPool)
|
||||
else
|
||||
@domNode.classList.add('editor-contents')
|
||||
@overlayManager = new OverlayManager(@presenter, @domNode, @views)
|
||||
@ -82,7 +88,10 @@ class TextEditorComponent
|
||||
@linesComponent = new LinesComponent({@presenter, @hostElement, @useShadowDOM, @domElementPool, @assert, @grammars})
|
||||
@scrollViewNode.appendChild(@linesComponent.getDomNode())
|
||||
|
||||
@linesYardstick = new LinesYardstick(@editor, @linesComponent, @grammars)
|
||||
if @blockDecorationsComponent?
|
||||
@linesComponent.getDomNode().appendChild(@blockDecorationsComponent.getDomNode())
|
||||
|
||||
@linesYardstick = new LinesYardstick(@editor, @linesComponent, lineTopIndex, @grammars)
|
||||
@presenter.setLinesYardstick(@linesYardstick)
|
||||
|
||||
@horizontalScrollbarComponent = new ScrollbarComponent({orientation: 'horizontal', onScroll: @onHorizontalScroll})
|
||||
@ -158,6 +167,7 @@ class TextEditorComponent
|
||||
|
||||
@hiddenInputComponent.updateSync(@newState)
|
||||
@linesComponent.updateSync(@newState)
|
||||
@blockDecorationsComponent?.updateSync(@newState)
|
||||
@horizontalScrollbarComponent.updateSync(@newState)
|
||||
@verticalScrollbarComponent.updateSync(@newState)
|
||||
@scrollbarCornerComponent.updateSync(@newState)
|
||||
@ -177,6 +187,7 @@ class TextEditorComponent
|
||||
|
||||
readAfterUpdateSync: =>
|
||||
@overlayManager?.measureOverlays()
|
||||
@blockDecorationsComponent?.measureBlockDecorations() if @isVisible()
|
||||
|
||||
mountGutterContainerComponent: ->
|
||||
@gutterContainerComponent = new GutterContainerComponent({@editor, @onLineNumberGutterMouseDown, @domElementPool, @views})
|
||||
@ -279,13 +290,13 @@ class TextEditorComponent
|
||||
observeConfig: ->
|
||||
@disposables.add @config.onDidChange 'editor.fontSize', =>
|
||||
@sampleFontStyling()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
@disposables.add @config.onDidChange 'editor.fontFamily', =>
|
||||
@sampleFontStyling()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
@disposables.add @config.onDidChange 'editor.lineHeight', =>
|
||||
@sampleFontStyling()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
|
||||
onGrammarChanged: =>
|
||||
if @scopedConfigDisposables?
|
||||
@ -434,12 +445,17 @@ class TextEditorComponent
|
||||
getVisibleRowRange: ->
|
||||
@presenter.getVisibleRowRange()
|
||||
|
||||
pixelPositionForScreenPosition: (screenPosition, clip) ->
|
||||
pixelPositionForScreenPosition: (screenPosition, clip=true) ->
|
||||
screenPosition = Point.fromObject(screenPosition)
|
||||
screenPosition = @editor.clipScreenPosition(screenPosition) if clip
|
||||
|
||||
unless @presenter.isRowVisible(screenPosition.row)
|
||||
@presenter.setScreenRowsToMeasure([screenPosition.row])
|
||||
|
||||
unless @linesComponent.lineNodeForLineIdAndScreenRow(@presenter.lineIdForScreenRow(screenPosition.row), screenPosition.row)?
|
||||
@updateSyncPreMeasurement()
|
||||
|
||||
pixelPosition = @linesYardstick.pixelPositionForScreenPosition(screenPosition, clip)
|
||||
pixelPosition = @linesYardstick.pixelPositionForScreenPosition(screenPosition)
|
||||
@presenter.clearScreenRowsToMeasure()
|
||||
pixelPosition
|
||||
|
||||
@ -480,6 +496,9 @@ class TextEditorComponent
|
||||
@editor.screenPositionForBufferPosition(bufferPosition)
|
||||
)
|
||||
|
||||
invalidateBlockDecorationDimensions: ->
|
||||
@presenter.invalidateBlockDecorationDimensions(arguments...)
|
||||
|
||||
onMouseDown: (event) =>
|
||||
unless event.button is 0 or (event.button is 1 and process.platform is 'linux')
|
||||
# Only handle mouse down events for left mouse button on all platforms
|
||||
@ -597,7 +616,7 @@ class TextEditorComponent
|
||||
handleStylingChange: =>
|
||||
@sampleFontStyling()
|
||||
@sampleBackgroundColors()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
|
||||
handleDragUntilMouseUp: (dragHandler) ->
|
||||
dragging = false
|
||||
@ -751,7 +770,7 @@ class TextEditorComponent
|
||||
if @fontSize isnt oldFontSize or @fontFamily isnt oldFontFamily or @lineHeight isnt oldLineHeight
|
||||
@clearPoolAfterUpdate = true
|
||||
@measureLineHeightAndDefaultCharWidth()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
|
||||
sampleBackgroundColors: (suppressUpdate) ->
|
||||
{backgroundColor} = getComputedStyle(@hostElement)
|
||||
@ -861,7 +880,7 @@ class TextEditorComponent
|
||||
setFontSize: (fontSize) ->
|
||||
@getTopmostDOMNode().style.fontSize = fontSize + 'px'
|
||||
@sampleFontStyling()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
|
||||
getFontFamily: ->
|
||||
getComputedStyle(@getTopmostDOMNode()).fontFamily
|
||||
@ -869,16 +888,16 @@ class TextEditorComponent
|
||||
setFontFamily: (fontFamily) ->
|
||||
@getTopmostDOMNode().style.fontFamily = fontFamily
|
||||
@sampleFontStyling()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
|
||||
setLineHeight: (lineHeight) ->
|
||||
@getTopmostDOMNode().style.lineHeight = lineHeight
|
||||
@sampleFontStyling()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
|
||||
invalidateCharacterWidths: ->
|
||||
invalidateMeasurements: ->
|
||||
@linesYardstick.invalidateCache()
|
||||
@presenter.characterWidthsChanged()
|
||||
@presenter.measurementsChanged()
|
||||
|
||||
setShowIndentGuide: (showIndentGuide) ->
|
||||
@config.set("editor.showIndentGuide", showIndentGuide)
|
||||
|
@ -347,4 +347,13 @@ class TextEditorElement extends HTMLElement
|
||||
getHeight: ->
|
||||
@offsetHeight
|
||||
|
||||
# Experimental: Invalidate the passed block {Decoration} dimensions, forcing
|
||||
# them to be recalculated and the surrounding content to be adjusted on the
|
||||
# next animation frame.
|
||||
#
|
||||
# * {blockDecoration} A {Decoration} representing the block decoration you
|
||||
# want to update the dimensions of.
|
||||
invalidateBlockDecorationDimensions: ->
|
||||
@component.invalidateBlockDecorationDimensions(arguments...)
|
||||
|
||||
module.exports = TextEditorElement = document.registerElement 'atom-text-editor', prototype: TextEditorElement.prototype
|
||||
|
@ -13,7 +13,7 @@ class TextEditorPresenter
|
||||
minimumReflowInterval: 200
|
||||
|
||||
constructor: (params) ->
|
||||
{@model, @config} = params
|
||||
{@model, @config, @lineTopIndex} = params
|
||||
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @tileSize} = params
|
||||
{@contentFrameWidth} = params
|
||||
|
||||
@ -28,6 +28,9 @@ class TextEditorPresenter
|
||||
@lineDecorationsByScreenRow = {}
|
||||
@lineNumberDecorationsByScreenRow = {}
|
||||
@customGutterDecorationsByGutterName = {}
|
||||
@observedBlockDecorations = new Set()
|
||||
@invalidatedDimensionsByBlockDecoration = new Set()
|
||||
@invalidateAllBlockDecorationsDimensions = false
|
||||
@screenRowsToMeasure = []
|
||||
@transferMeasurementsToModel()
|
||||
@transferMeasurementsFromModel()
|
||||
@ -85,6 +88,7 @@ class TextEditorPresenter
|
||||
if @shouldUpdateDecorations
|
||||
@fetchDecorations()
|
||||
@updateLineDecorations()
|
||||
@updateBlockDecorations()
|
||||
|
||||
@updateTilesState()
|
||||
|
||||
@ -126,7 +130,8 @@ class TextEditorPresenter
|
||||
@shouldUpdateDecorations = true
|
||||
|
||||
observeModel: ->
|
||||
@disposables.add @model.onDidChange =>
|
||||
@disposables.add @model.onDidChange ({start, end, screenDelta}) =>
|
||||
@spliceBlockDecorationsInRange(start, end, screenDelta)
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
@ -134,6 +139,11 @@ class TextEditorPresenter
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
@disposables.add @model.onDidAddDecoration(@didAddBlockDecoration.bind(this))
|
||||
|
||||
for decoration in @model.getDecorations({type: 'block'})
|
||||
this.didAddBlockDecoration(decoration)
|
||||
|
||||
@disposables.add @model.onDidChangeGrammar(@didChangeGrammar.bind(this))
|
||||
@disposables.add @model.onDidChangePlaceholderText(@emitDidUpdateState.bind(this))
|
||||
@disposables.add @model.onDidChangeMini =>
|
||||
@ -192,6 +202,7 @@ class TextEditorPresenter
|
||||
highlights: {}
|
||||
overlays: {}
|
||||
cursors: {}
|
||||
blockDecorations: {}
|
||||
gutters: []
|
||||
# Shared state that is copied into ``@state.gutters`.
|
||||
@sharedGutterStyles = {}
|
||||
@ -327,6 +338,7 @@ class TextEditorPresenter
|
||||
zIndex = 0
|
||||
|
||||
for tileStartRow in [@tileForRow(endRow)..@tileForRow(startRow)] by -@tileSize
|
||||
tileEndRow = @constrainRow(tileStartRow + @tileSize)
|
||||
rowsWithinTile = []
|
||||
|
||||
while screenRowIndex >= 0
|
||||
@ -337,17 +349,21 @@ class TextEditorPresenter
|
||||
|
||||
continue if rowsWithinTile.length is 0
|
||||
|
||||
top = Math.round(@lineTopIndex.pixelPositionBeforeBlocksForRow(tileStartRow))
|
||||
bottom = Math.round(@lineTopIndex.pixelPositionBeforeBlocksForRow(tileEndRow))
|
||||
height = bottom - top
|
||||
|
||||
tile = @state.content.tiles[tileStartRow] ?= {}
|
||||
tile.top = tileStartRow * @lineHeight - @scrollTop
|
||||
tile.top = top - @scrollTop
|
||||
tile.left = -@scrollLeft
|
||||
tile.height = @tileSize * @lineHeight
|
||||
tile.height = height
|
||||
tile.display = "block"
|
||||
tile.zIndex = zIndex
|
||||
tile.highlights ?= {}
|
||||
|
||||
gutterTile = @lineNumberGutter.tiles[tileStartRow] ?= {}
|
||||
gutterTile.top = tileStartRow * @lineHeight - @scrollTop
|
||||
gutterTile.height = @tileSize * @lineHeight
|
||||
gutterTile.top = top - @scrollTop
|
||||
gutterTile.height = height
|
||||
gutterTile.display = "block"
|
||||
gutterTile.zIndex = zIndex
|
||||
|
||||
@ -380,10 +396,16 @@ class TextEditorPresenter
|
||||
throw new Error("No line exists for row #{screenRow}. Last screen row: #{@model.getLastScreenRow()}")
|
||||
|
||||
visibleLineIds[line.id] = true
|
||||
precedingBlockDecorations = @precedingBlockDecorationsByScreenRow[screenRow] ? []
|
||||
followingBlockDecorations = @followingBlockDecorationsByScreenRow[screenRow] ? []
|
||||
if tileState.lines.hasOwnProperty(line.id)
|
||||
lineState = tileState.lines[line.id]
|
||||
lineState.screenRow = screenRow
|
||||
lineState.decorationClasses = @lineDecorationClassesForRow(screenRow)
|
||||
lineState.precedingBlockDecorations = precedingBlockDecorations
|
||||
lineState.followingBlockDecorations = followingBlockDecorations
|
||||
lineState.hasPrecedingBlockDecorations = precedingBlockDecorations.length > 0
|
||||
lineState.hasFollowingBlockDecorations = followingBlockDecorations.length > 0
|
||||
else
|
||||
tileState.lines[line.id] =
|
||||
screenRow: screenRow
|
||||
@ -400,6 +422,10 @@ class TextEditorPresenter
|
||||
tabLength: line.tabLength
|
||||
fold: line.fold
|
||||
decorationClasses: @lineDecorationClassesForRow(screenRow)
|
||||
precedingBlockDecorations: precedingBlockDecorations
|
||||
followingBlockDecorations: followingBlockDecorations
|
||||
hasPrecedingBlockDecorations: precedingBlockDecorations.length > 0
|
||||
hasFollowingBlockDecorations: followingBlockDecorations.length > 0
|
||||
|
||||
for id, line of tileState.lines
|
||||
delete tileState.lines[id] unless visibleLineIds.hasOwnProperty(id)
|
||||
@ -433,7 +459,7 @@ class TextEditorPresenter
|
||||
else
|
||||
screenPosition = decoration.getMarker().getHeadScreenPosition()
|
||||
|
||||
pixelPosition = @pixelPositionForScreenPosition(screenPosition, true)
|
||||
pixelPosition = @pixelPositionForScreenPosition(screenPosition)
|
||||
|
||||
top = pixelPosition.top + @lineHeight
|
||||
left = pixelPosition.left + @gutterWidth
|
||||
@ -536,9 +562,11 @@ class TextEditorPresenter
|
||||
|
||||
continue unless @gutterIsVisible(gutter)
|
||||
for decorationId, {properties, screenRange} of @customGutterDecorationsByGutterName[gutterName]
|
||||
top = @lineTopIndex.pixelPositionAfterBlocksForRow(screenRange.start.row)
|
||||
bottom = @lineTopIndex.pixelPositionBeforeBlocksForRow(screenRange.end.row + 1)
|
||||
@customGutterDecorations[gutterName][decorationId] =
|
||||
top: @lineHeight * screenRange.start.row
|
||||
height: @lineHeight * screenRange.getRowCount()
|
||||
top: top
|
||||
height: bottom - top
|
||||
item: properties.item
|
||||
class: properties.class
|
||||
|
||||
@ -586,8 +614,13 @@ class TextEditorPresenter
|
||||
line = @model.tokenizedLineForScreenRow(screenRow)
|
||||
decorationClasses = @lineNumberDecorationClassesForRow(screenRow)
|
||||
foldable = @model.isFoldableAtScreenRow(screenRow)
|
||||
blockDecorationsBeforeCurrentScreenRowHeight = @lineTopIndex.pixelPositionAfterBlocksForRow(screenRow) - @lineTopIndex.pixelPositionBeforeBlocksForRow(screenRow)
|
||||
blockDecorationsHeight = blockDecorationsBeforeCurrentScreenRowHeight
|
||||
if screenRow % @tileSize isnt 0
|
||||
blockDecorationsAfterPreviousScreenRowHeight = @lineTopIndex.pixelPositionBeforeBlocksForRow(screenRow) - @lineHeight - @lineTopIndex.pixelPositionAfterBlocksForRow(screenRow - 1)
|
||||
blockDecorationsHeight += blockDecorationsAfterPreviousScreenRowHeight
|
||||
|
||||
tileState.lineNumbers[line.id] = {screenRow, bufferRow, softWrapped, decorationClasses, foldable}
|
||||
tileState.lineNumbers[line.id] = {screenRow, bufferRow, softWrapped, decorationClasses, foldable, blockDecorationsHeight}
|
||||
visibleLineNumberIds[line.id] = true
|
||||
|
||||
for id of tileState.lineNumbers
|
||||
@ -598,16 +631,15 @@ class TextEditorPresenter
|
||||
updateStartRow: ->
|
||||
return unless @scrollTop? and @lineHeight?
|
||||
|
||||
startRow = Math.floor(@scrollTop / @lineHeight)
|
||||
@startRow = Math.max(0, startRow)
|
||||
@startRow = Math.max(0, @lineTopIndex.rowForPixelPosition(@scrollTop))
|
||||
|
||||
updateEndRow: ->
|
||||
return unless @scrollTop? and @lineHeight? and @height?
|
||||
|
||||
startRow = Math.max(0, Math.floor(@scrollTop / @lineHeight))
|
||||
visibleLinesCount = Math.ceil(@height / @lineHeight) + 1
|
||||
endRow = startRow + visibleLinesCount
|
||||
@endRow = Math.min(@model.getScreenLineCount(), endRow)
|
||||
@endRow = Math.min(
|
||||
@model.getScreenLineCount(),
|
||||
@lineTopIndex.rowForPixelPosition(@scrollTop + @height + @lineHeight - 1) + 1
|
||||
)
|
||||
|
||||
updateRowsPerPage: ->
|
||||
rowsPerPage = Math.floor(@getClientHeight() / @lineHeight)
|
||||
@ -639,7 +671,7 @@ class TextEditorPresenter
|
||||
updateVerticalDimensions: ->
|
||||
if @lineHeight?
|
||||
oldContentHeight = @contentHeight
|
||||
@contentHeight = @lineHeight * @model.getScreenLineCount()
|
||||
@contentHeight = Math.round(@lineTopIndex.pixelPositionAfterBlocksForRow(@model.getScreenLineCount()))
|
||||
|
||||
if @contentHeight isnt oldContentHeight
|
||||
@updateHeight()
|
||||
@ -649,8 +681,10 @@ class TextEditorPresenter
|
||||
updateHorizontalDimensions: ->
|
||||
if @baseCharacterWidth?
|
||||
oldContentWidth = @contentWidth
|
||||
clip = @model.tokenizedLineForScreenRow(@model.getLongestScreenRow())?.isSoftWrapped()
|
||||
@contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), @model.getMaxScreenLineLength()], clip).left
|
||||
rightmostPosition = Point(@model.getLongestScreenRow(), @model.getMaxScreenLineLength())
|
||||
if @model.tokenizedLineForScreenRow(rightmostPosition.row)?.isSoftWrapped()
|
||||
rightmostPosition = @model.clipScreenPosition(rightmostPosition)
|
||||
@contentWidth = @pixelPositionForScreenPosition(rightmostPosition).left
|
||||
@contentWidth += @scrollLeft
|
||||
@contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width
|
||||
|
||||
@ -804,6 +838,7 @@ class TextEditorPresenter
|
||||
didStopScrolling: ->
|
||||
if @mouseWheelScreenRow?
|
||||
@mouseWheelScreenRow = null
|
||||
@shouldUpdateDecorations = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
@ -896,12 +931,15 @@ class TextEditorPresenter
|
||||
@editorWidthInChars = null
|
||||
@updateScrollbarDimensions()
|
||||
@updateClientWidth()
|
||||
@invalidateAllBlockDecorationsDimensions = true
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
setBoundingClientRect: (boundingClientRect) ->
|
||||
unless @clientRectsEqual(@boundingClientRect, boundingClientRect)
|
||||
@boundingClientRect = boundingClientRect
|
||||
@invalidateAllBlockDecorationsDimensions = true
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
clientRectsEqual: (clientRectA, clientRectB) ->
|
||||
@ -915,6 +953,8 @@ class TextEditorPresenter
|
||||
if @windowWidth isnt width or @windowHeight isnt height
|
||||
@windowWidth = width
|
||||
@windowHeight = height
|
||||
@invalidateAllBlockDecorationsDimensions = true
|
||||
@shouldUpdateDecorations = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
@ -939,6 +979,8 @@ class TextEditorPresenter
|
||||
setLineHeight: (lineHeight) ->
|
||||
unless @lineHeight is lineHeight
|
||||
@lineHeight = lineHeight
|
||||
@model.setLineHeightInPixels(@lineHeight)
|
||||
@lineTopIndex.setDefaultLineHeight(@lineHeight)
|
||||
@restoreScrollTopIfNeeded()
|
||||
@model.setLineHeightInPixels(lineHeight)
|
||||
@shouldUpdateDecorations = true
|
||||
@ -957,18 +999,19 @@ class TextEditorPresenter
|
||||
@koreanCharWidth = koreanCharWidth
|
||||
@model.setDefaultCharWidth(baseCharacterWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth)
|
||||
@restoreScrollLeftIfNeeded()
|
||||
@characterWidthsChanged()
|
||||
@measurementsChanged()
|
||||
|
||||
characterWidthsChanged: ->
|
||||
measurementsChanged: ->
|
||||
@invalidateAllBlockDecorationsDimensions = true
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
hasPixelPositionRequirements: ->
|
||||
@lineHeight? and @baseCharacterWidth?
|
||||
|
||||
pixelPositionForScreenPosition: (screenPosition, clip=true) ->
|
||||
pixelPositionForScreenPosition: (screenPosition) ->
|
||||
position =
|
||||
@linesYardstick.pixelPositionForScreenPosition(screenPosition, clip, true)
|
||||
@linesYardstick.pixelPositionForScreenPosition(screenPosition)
|
||||
position.top -= @getScrollTop()
|
||||
position.left -= @getScrollLeft()
|
||||
|
||||
@ -987,14 +1030,14 @@ class TextEditorPresenter
|
||||
lineHeight = @model.getLineHeightInPixels()
|
||||
|
||||
if screenRange.end.row > screenRange.start.row
|
||||
top = @linesYardstick.pixelPositionForScreenPosition(screenRange.start, true).top
|
||||
top = @linesYardstick.pixelPositionForScreenPosition(screenRange.start).top
|
||||
left = 0
|
||||
height = (screenRange.end.row - screenRange.start.row + 1) * lineHeight
|
||||
width = @getScrollWidth()
|
||||
else
|
||||
{top, left} = @linesYardstick.pixelPositionForScreenPosition(screenRange.start, false)
|
||||
{top, left} = @linesYardstick.pixelPositionForScreenPosition(screenRange.start)
|
||||
height = lineHeight
|
||||
width = @linesYardstick.pixelPositionForScreenPosition(screenRange.end, false).left - left
|
||||
width = @linesYardstick.pixelPositionForScreenPosition(screenRange.end).left - left
|
||||
|
||||
{top, left, width, height}
|
||||
|
||||
@ -1012,6 +1055,43 @@ class TextEditorPresenter
|
||||
return unless 0 <= @startRow <= @endRow <= Infinity
|
||||
@decorations = @model.decorationsStateForScreenRowRange(@startRow, @endRow - 1)
|
||||
|
||||
updateBlockDecorations: ->
|
||||
@blockDecorationsToRenderById = {}
|
||||
@precedingBlockDecorationsByScreenRow = {}
|
||||
@followingBlockDecorationsByScreenRow = {}
|
||||
visibleDecorationsByMarkerId = @model.decorationsForScreenRowRange(@getStartTileRow(), @getEndTileRow() + @tileSize - 1)
|
||||
|
||||
if @invalidateAllBlockDecorationsDimensions
|
||||
for decoration in @model.getDecorations(type: 'block')
|
||||
@invalidatedDimensionsByBlockDecoration.add(decoration)
|
||||
@invalidateAllBlockDecorationsDimensions = false
|
||||
|
||||
for markerId, decorations of visibleDecorationsByMarkerId
|
||||
for decoration in decorations when decoration.isType('block')
|
||||
@updateBlockDecorationState(decoration, true)
|
||||
|
||||
@invalidatedDimensionsByBlockDecoration.forEach (decoration) =>
|
||||
@updateBlockDecorationState(decoration, false)
|
||||
|
||||
for decorationId, decorationState of @state.content.blockDecorations
|
||||
continue if @blockDecorationsToRenderById[decorationId]
|
||||
continue if decorationState.screenRow is @mouseWheelScreenRow
|
||||
|
||||
delete @state.content.blockDecorations[decorationId]
|
||||
|
||||
updateBlockDecorationState: (decoration, isVisible) ->
|
||||
return if @blockDecorationsToRenderById[decoration.getId()]
|
||||
|
||||
screenRow = decoration.getMarker().getHeadScreenPosition().row
|
||||
if decoration.getProperties().position is "before"
|
||||
@precedingBlockDecorationsByScreenRow[screenRow] ?= []
|
||||
@precedingBlockDecorationsByScreenRow[screenRow].push(decoration)
|
||||
else
|
||||
@followingBlockDecorationsByScreenRow[screenRow] ?= []
|
||||
@followingBlockDecorationsByScreenRow[screenRow].push(decoration)
|
||||
@state.content.blockDecorations[decoration.getId()] = {decoration, screenRow, isVisible}
|
||||
@blockDecorationsToRenderById[decoration.getId()] = true
|
||||
|
||||
updateLineDecorations: ->
|
||||
@lineDecorationsByScreenRow = {}
|
||||
@lineNumberDecorationsByScreenRow = {}
|
||||
@ -1134,13 +1214,13 @@ class TextEditorPresenter
|
||||
screenRange.end.column = 0
|
||||
|
||||
repositionRegionWithinTile: (region, tileStartRow) ->
|
||||
region.top += @scrollTop - tileStartRow * @lineHeight
|
||||
region.top += @scrollTop - @lineTopIndex.pixelPositionBeforeBlocksForRow(tileStartRow)
|
||||
region.left += @scrollLeft
|
||||
|
||||
buildHighlightRegions: (screenRange) ->
|
||||
lineHeightInPixels = @lineHeight
|
||||
startPixelPosition = @pixelPositionForScreenPosition(screenRange.start, false)
|
||||
endPixelPosition = @pixelPositionForScreenPosition(screenRange.end, false)
|
||||
startPixelPosition = @pixelPositionForScreenPosition(screenRange.start)
|
||||
endPixelPosition = @pixelPositionForScreenPosition(screenRange.end)
|
||||
spannedRows = screenRange.end.row - screenRange.start.row + 1
|
||||
|
||||
regions = []
|
||||
@ -1204,6 +1284,73 @@ class TextEditorPresenter
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
setBlockDecorationDimensions: (decoration, width, height) ->
|
||||
return unless @observedBlockDecorations.has(decoration)
|
||||
|
||||
@lineTopIndex.resizeBlock(decoration.getId(), height)
|
||||
|
||||
@invalidatedDimensionsByBlockDecoration.delete(decoration)
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
invalidateBlockDecorationDimensions: (decoration) ->
|
||||
@invalidatedDimensionsByBlockDecoration.add(decoration)
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
spliceBlockDecorationsInRange: (start, end, screenDelta) ->
|
||||
return if screenDelta is 0
|
||||
|
||||
oldExtent = end - start
|
||||
newExtent = end - start + screenDelta
|
||||
invalidatedBlockDecorationIds = @lineTopIndex.splice(start, oldExtent, newExtent)
|
||||
invalidatedBlockDecorationIds.forEach (id) =>
|
||||
decoration = @model.decorationForId(id)
|
||||
newScreenPosition = decoration.getMarker().getHeadScreenPosition()
|
||||
@lineTopIndex.moveBlock(id, newScreenPosition.row)
|
||||
@invalidatedDimensionsByBlockDecoration.add(decoration)
|
||||
|
||||
didAddBlockDecoration: (decoration) ->
|
||||
return if not decoration.isType('block') or @observedBlockDecorations.has(decoration)
|
||||
|
||||
didMoveDisposable = decoration.getMarker().bufferMarker.onDidChange (markerEvent) =>
|
||||
@didMoveBlockDecoration(decoration, markerEvent)
|
||||
|
||||
didDestroyDisposable = decoration.onDidDestroy =>
|
||||
@disposables.remove(didMoveDisposable)
|
||||
@disposables.remove(didDestroyDisposable)
|
||||
didMoveDisposable.dispose()
|
||||
didDestroyDisposable.dispose()
|
||||
@didDestroyBlockDecoration(decoration)
|
||||
|
||||
isAfter = decoration.getProperties().position is "after"
|
||||
@lineTopIndex.insertBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition().row, 0, isAfter)
|
||||
|
||||
@observedBlockDecorations.add(decoration)
|
||||
@invalidateBlockDecorationDimensions(decoration)
|
||||
@disposables.add(didMoveDisposable)
|
||||
@disposables.add(didDestroyDisposable)
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
didMoveBlockDecoration: (decoration, markerEvent) ->
|
||||
# Don't move blocks after a text change, because we already splice on buffer
|
||||
# change.
|
||||
return if markerEvent.textChanged
|
||||
|
||||
@lineTopIndex.moveBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition().row)
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
didDestroyBlockDecoration: (decoration) ->
|
||||
return unless @observedBlockDecorations.has(decoration)
|
||||
|
||||
@lineTopIndex.removeBlock(decoration.getId())
|
||||
@observedBlockDecorations.delete(decoration)
|
||||
@invalidatedDimensionsByBlockDecoration.delete(decoration)
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
observeCursor: (cursor) ->
|
||||
didChangePositionDisposable = cursor.onDidChangePosition =>
|
||||
@pauseCursorBlinking()
|
||||
@ -1264,7 +1411,7 @@ class TextEditorPresenter
|
||||
@emitDidUpdateState()
|
||||
|
||||
didChangeFirstVisibleScreenRow: (screenRow) ->
|
||||
@setScrollTop(screenRow * @lineHeight)
|
||||
@setScrollTop(@lineTopIndex.pixelPositionAfterBlocksForRow(screenRow))
|
||||
|
||||
getVerticalScrollMarginInPixels: ->
|
||||
Math.round(@model.getVerticalScrollMargin() * @lineHeight)
|
||||
@ -1285,8 +1432,8 @@ class TextEditorPresenter
|
||||
|
||||
verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels()
|
||||
|
||||
top = screenRange.start.row * @lineHeight
|
||||
bottom = (screenRange.end.row + 1) * @lineHeight
|
||||
top = @lineTopIndex.pixelPositionAfterBlocksForRow(screenRange.start.row)
|
||||
bottom = @lineTopIndex.pixelPositionAfterBlocksForRow(screenRange.end.row) + @lineHeight
|
||||
|
||||
if options?.center
|
||||
desiredScrollCenter = (top + bottom) / 2
|
||||
@ -1358,7 +1505,7 @@ class TextEditorPresenter
|
||||
|
||||
restoreScrollTopIfNeeded: ->
|
||||
unless @scrollTop?
|
||||
@updateScrollTop(@model.getFirstVisibleScreenRow() * @lineHeight)
|
||||
@updateScrollTop(@lineTopIndex.pixelPositionAfterBlocksForRow(@model.getFirstVisibleScreenRow()))
|
||||
|
||||
restoreScrollLeftIfNeeded: ->
|
||||
unless @scrollLeft?
|
||||
@ -1375,3 +1522,6 @@ class TextEditorPresenter
|
||||
|
||||
isRowVisible: (row) ->
|
||||
@startRow <= row < @endRow
|
||||
|
||||
lineIdForScreenRow: (screenRow) ->
|
||||
@model.tokenizedLineForScreenRow(screenRow)?.id
|
||||
|
@ -668,8 +668,9 @@ class TextEditor extends Model
|
||||
isPending: -> Boolean(@pending)
|
||||
|
||||
# Copies the current file path to the native clipboard.
|
||||
copyPathToClipboard: ->
|
||||
copyPathToClipboard: (relative = false) ->
|
||||
if filePath = @getPath()
|
||||
filePath = atom.project.relativize(filePath) if relative
|
||||
@clipboard.write(filePath)
|
||||
|
||||
###
|
||||
@ -1438,6 +1439,8 @@ class TextEditor extends Model
|
||||
# * __gutter__: A decoration that tracks a {TextEditorMarker} in a {Gutter}. Gutter
|
||||
# decorations are created by calling {Gutter::decorateMarker} on the
|
||||
# desired `Gutter` instance.
|
||||
# * __block__: Positions the view associated with the given item before or
|
||||
# after the row of the given `TextEditorMarker`.
|
||||
#
|
||||
# ## Arguments
|
||||
#
|
||||
@ -1457,11 +1460,14 @@ class TextEditor extends Model
|
||||
# property.
|
||||
# * `gutter` Tracks a {TextEditorMarker} in a {Gutter}. Created by calling
|
||||
# {Gutter::decorateMarker} on the desired `Gutter` instance.
|
||||
# * `block` Positions the view associated with the given item before or
|
||||
# after the row of the given `TextEditorMarker`, depending on the `position`
|
||||
# property.
|
||||
# * `class` This CSS class will be applied to the decorated line number,
|
||||
# line, highlight, or overlay.
|
||||
# * `item` (optional) An {HTMLElement} or a model {Object} with a
|
||||
# corresponding view registered. Only applicable to the `gutter` and
|
||||
# `overlay` types.
|
||||
# corresponding view registered. Only applicable to the `gutter`,
|
||||
# `overlay` and `block` types.
|
||||
# * `onlyHead` (optional) If `true`, the decoration will only be applied to
|
||||
# the head of the `TextEditorMarker`. Only applicable to the `line` and
|
||||
# `line-number` types.
|
||||
@ -1471,9 +1477,10 @@ class TextEditor extends Model
|
||||
# * `onlyNonEmpty` (optional) If `true`, the decoration will only be applied
|
||||
# if the associated `TextEditorMarker` is non-empty. Only applicable to the
|
||||
# `gutter`, `line`, and `line-number` types.
|
||||
# * `position` (optional) Only applicable to decorations of type `overlay`,
|
||||
# controls where the overlay view is positioned relative to the `TextEditorMarker`.
|
||||
# Values can be `'head'` (the default), or `'tail'`.
|
||||
# * `position` (optional) Only applicable to decorations of type `overlay` and `block`,
|
||||
# controls where the view is positioned relative to the `TextEditorMarker`.
|
||||
# Values can be `'head'` (the default) or `'tail'` for overlay decorations, and
|
||||
# `'before'` (the default) or `'after'` for block decorations.
|
||||
#
|
||||
# Returns a {Decoration} object
|
||||
decorateMarker: (marker, decorationParams) ->
|
||||
@ -3052,7 +3059,7 @@ class TextEditor extends Model
|
||||
|
||||
# Essential: Scrolls the editor to the given screen position.
|
||||
#
|
||||
# * `screenPosition` An object that represents a buffer position. It can be either
|
||||
# * `screenPosition` An object that represents a screen position. It can be either
|
||||
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
|
||||
# * `options` (optional) {Object}
|
||||
# * `center` Center the editor around the position if possible. (default: false)
|
||||
|
@ -475,7 +475,7 @@ class Workspace extends Model
|
||||
when 'EACCES'
|
||||
@notificationManager.addWarning("Permission denied '#{error.path}'")
|
||||
return Promise.resolve()
|
||||
when 'EPERM', 'EBUSY', 'ENXIO', 'EIO', 'ENOTCONN', 'UNKNOWN', 'ECONNRESET', 'EINVAL', 'EMFILE', 'ENOTDIR'
|
||||
when 'EPERM', 'EBUSY', 'ENXIO', 'EIO', 'ENOTCONN', 'UNKNOWN', 'ECONNRESET', 'EINVAL', 'EMFILE', 'ENOTDIR', 'EAGAIN'
|
||||
@notificationManager.addWarning("Unable to open '#{error.path ? uri}'", detail: error.message)
|
||||
return Promise.resolve()
|
||||
else
|
||||
|
Loading…
Reference in New Issue
Block a user