Comparar commits
173 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 88df4d2f3e | |||
| 4136ff566b | |||
| 2fe523a664 | |||
| 148180adda | |||
| 01a4032895 | |||
| 2b2b65ec82 | |||
| 5ffc063f0c | |||
| dee0771dd7 | |||
| aef6991ca8 | |||
| e8db3e97ce | |||
| b1dd4f2e8e | |||
| 633b08b9de | |||
| 1228435f9b | |||
| 02b5280587 | |||
| 2e47207701 | |||
| 593b5b4e36 | |||
| 1896c94775 | |||
| 4dc20e2027 | |||
| 238e291888 | |||
| 5ccac143aa | |||
| ae97244664 | |||
| 859e3cd038 | |||
| 07577ff944 | |||
| 72b1821828 | |||
| 781a51ac53 | |||
| 9055e650c6 | |||
| 5195a4aaa7 | |||
| 5e4f34f92c | |||
| dc7b549017 | |||
| 29d5f63cd9 | |||
| 33b4ec8e25 | |||
| 85abed2406 | |||
| 520ece4b13 | |||
| 184068dc55 | |||
| f27b897e91 | |||
| 6e201104bc | |||
| 908a2978ae | |||
| 2eb5ef0816 | |||
| 0c5f2cd067 | |||
| d1e60fb2a5 | |||
| bee7be1d1a | |||
| bee4c9df8a | |||
| c5815d2af9 | |||
| 77717d3eff | |||
| d3e0005b33 | |||
| 8295019891 | |||
| 2edcc517b1 | |||
| 9083103bb3 | |||
| bc391094df | |||
| ffba81a962 | |||
| 1e1f4cf173 | |||
| 7f04149f8d | |||
| cd1fb99142 | |||
| 388763e7cd | |||
| f22e4225c3 | |||
| db4b99d27b | |||
| 17f9cc49f2 | |||
| 609855af3c | |||
| 8410d8587b | |||
| 33c9d5ae24 | |||
| 0c48821465 | |||
| 43259f5c51 | |||
| fe2cb046c3 | |||
| de132d79a4 | |||
| 5af181ffb5 | |||
| 355f54ba00 | |||
| b56d4c6181 | |||
| ad9e2ab869 | |||
| a497b0f90f | |||
| e4fd80399d | |||
| c4c5d72bf1 | |||
| 0504244066 | |||
| 8560526158 | |||
| ec8805e99e | |||
| f8ec2e6da4 | |||
| 2a28eafd04 | |||
| a6bf1af2d5 | |||
| 8eae66fc49 | |||
| 02757fc2de | |||
| fc37ac37bd | |||
| 63587abe97 | |||
| d5ea766541 | |||
| bd0643eda4 | |||
| 7a9710b8c3 | |||
| 468c6598db | |||
| ddb3cdc76f | |||
| 1c8e716cfd | |||
| f1f83a7d36 | |||
| 8d87eb2ed6 | |||
| 29d26a4fae | |||
| 5c6f711bf3 | |||
| 53c363b853 | |||
| bacd612c71 | |||
| 2cded15c4c | |||
| 2e73a46cbc | |||
| 64ae7bcdcc | |||
| abca7f778c | |||
| b7197145c8 | |||
| 7f53bb5753 | |||
| 8d38fc77d6 | |||
| 1975882b9e | |||
| 0bd4d31ab0 | |||
| 4192c121e5 | |||
| e89e2141d7 | |||
| 25c5458bd2 | |||
| ff0cddfd1d | |||
| a7ec7497e2 | |||
| 3790cdb262 | |||
| 4308ce7bb0 | |||
| b302fdc553 | |||
| 2fffbba503 | |||
| 9be1427891 | |||
| 4564a39392 | |||
| 068c1e6249 | |||
| 06a55250b9 | |||
| d8240628a7 | |||
| eaa7593b27 | |||
| 72e4be60c0 | |||
| f3a4d32a32 | |||
| edadedce7b | |||
| 89be77b0a9 | |||
| 99ba20ae0d | |||
| 0255e44f00 | |||
| 4832d36ac1 | |||
| 5d15af943e | |||
| 0a671fc386 | |||
| c06f5911c6 | |||
| 2af8404ea9 | |||
| 345d20dc4a | |||
| ef09fbbfb2 | |||
| a3784500ec | |||
| ef7f0ed9ed | |||
| 72c87bfbc9 | |||
| 54c7c1e98d | |||
| 359793c0b0 | |||
| 34ec15862f | |||
| 04bbe393d4 | |||
| 5b84aa7b18 | |||
| 7ba498a170 | |||
| c21f8a5a6c | |||
| 0312609e19 | |||
| 32fba97c3a | |||
| 5259d5b750 | |||
| dff27eba18 | |||
| d2908c75fc | |||
| 2c04bff0fa | |||
| 4f2f158d0d | |||
| 351dc58354 | |||
| 2867dd98e5 | |||
| 32a0804b9a | |||
| 408e62a993 | |||
| d4057d21c7 | |||
| 031ec9798a | |||
| 1ebdd801f5 | |||
| 084632a985 | |||
| 002e14990b | |||
| 3a3fc4b614 | |||
| b028673b5d | |||
| 6394814142 | |||
| e128212410 | |||
| 2d4360dcf0 | |||
| 120e2a3bdb | |||
| 7142022f05 | |||
| 25520a4cad | |||
| 56da6399b8 | |||
| a7379b067a | |||
| 8411d41621 | |||
| fc462fcd21 | |||
| 9e6756ed6d | |||
| 8d84a97b2b | |||
| e96d2dbd17 | |||
| 025370b9f8 | |||
| 1fe6c498ac |
+1
-1
@@ -6,6 +6,6 @@
|
||||
"url": "https://github.com/atom/atom.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"atom-package-manager": "0.68.0"
|
||||
"atom-package-manager": "0.69.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,10 @@ module.exports = (grunt) ->
|
||||
unless /.+\.plist/.test(sourcePath)
|
||||
grunt.file.copy(sourcePath, path.resolve(appDir, '..', subDirectory, filename))
|
||||
|
||||
if process.platform is 'win32'
|
||||
cp path.join('resources', 'win', 'msvcp100.dll'), path.join(shellAppDir, 'msvcp100.dll')
|
||||
cp path.join('resources', 'win', 'msvcr100.dll'), path.join(shellAppDir, 'msvcr100.dll')
|
||||
|
||||
dependencies = ['compile', "generate-license:save"]
|
||||
dependencies.push('copy-info-plist') if process.platform is 'darwin'
|
||||
dependencies.push('set-exe-icon') if process.platform is 'win32'
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
fs = require 'fs'
|
||||
path = require 'path'
|
||||
_ = require 'underscore-plus'
|
||||
fs = require 'fs-plus'
|
||||
runas = null
|
||||
|
||||
fillTemplate = (filePath, data) ->
|
||||
template = _.template(String(fs.readFileSync(filePath + '.in')))
|
||||
filled = template(data)
|
||||
fs.writeFileSync(filePath, filled)
|
||||
|
||||
module.exports = (grunt) ->
|
||||
{cp, mkdir, rm} = require('./task-helpers')(grunt)
|
||||
|
||||
@@ -25,12 +31,23 @@ module.exports = (grunt) ->
|
||||
binDir = path.join(installDir, 'bin')
|
||||
shareDir = path.join(installDir, 'share', 'atom')
|
||||
|
||||
iconName = path.join(shareDir,'resources','app','resources','atom.png')
|
||||
desktopFile = path.join('resources', 'linux', 'Atom.desktop')
|
||||
|
||||
mkdir binDir
|
||||
cp 'atom.sh', path.join(binDir, 'atom')
|
||||
rm shareDir
|
||||
mkdir path.dirname(shareDir)
|
||||
cp shellAppDir, shareDir
|
||||
|
||||
# Create Atom.desktop if installation in '/usr/local'
|
||||
applicationsDir = path.join('/usr','share','applications')
|
||||
tmpDir = if process.env.TMPDIR? then process.env.TMPDIR else '/tmp'
|
||||
if installDir.indexOf(tmpDir) isnt 0 and fs.isDirectorySync(applicationsDir)
|
||||
{description} = grunt.file.readJSON('package.json')
|
||||
fillTemplate(desktopFile, {description, installDir, iconName})
|
||||
cp desktopFile, path.join(applicationsDir,'Atom.desktop')
|
||||
|
||||
# Create relative symbol link for apm.
|
||||
process.chdir(binDir)
|
||||
rm('apm')
|
||||
|
||||
@@ -23,7 +23,9 @@ module.exports = (grunt) ->
|
||||
{name, version, description} = grunt.file.readJSON('package.json')
|
||||
section = 'devel'
|
||||
maintainer = 'GitHub <atom@github.com>'
|
||||
data = {name, version, description, section, arch, maintainer}
|
||||
installDir = '/usr'
|
||||
iconName = 'atom'
|
||||
data = {name, version, description, section, arch, maintainer, installDir, iconName}
|
||||
|
||||
control = path.join('resources', 'linux', 'debian', 'control')
|
||||
fillTemplate(control, data)
|
||||
|
||||
@@ -9,8 +9,11 @@
|
||||
* For 64-bit builds of node and native modules you **must** have the
|
||||
[Windows 7 64-bit SDK](http://www.microsoft.com/en-us/download/details.aspx?id=8279).
|
||||
You may also need the [compiler update for the Windows SDK 7.1](http://www.microsoft.com/en-us/download/details.aspx?id=4422)
|
||||
|
||||
* [Python](http://www.python.org/download/) v2.7.x
|
||||
* [Python](http://www.python.org/download/) v2.7.
|
||||
* The python.exe must be available at `%SystemDrive%\Python27\python.exe`.
|
||||
If it is installed elsewhere, you can create a symbolic link to the
|
||||
directory containing the python.exe using:
|
||||
`mklink /d %SystemDrive%\Python27 D:\elsewhere\Python27`
|
||||
* [GitHub for Windows](http://windows.github.com/)
|
||||
|
||||
### On Windows 8
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
'shift-down': 'core:select-down'
|
||||
'shift-left': 'core:select-left'
|
||||
'shift-right': 'core:select-right'
|
||||
'shift-pageup': 'core:select-page-up'
|
||||
'shift-pagedown': 'core:select-page-down'
|
||||
'delete': 'core:delete'
|
||||
'shift-delete': 'core:delete'
|
||||
'pageup': 'core:page-up'
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
'shift-down': 'core:select-down'
|
||||
'shift-left': 'core:select-left'
|
||||
'shift-right': 'core:select-right'
|
||||
'shift-pageup': 'core:select-page-up'
|
||||
'shift-pagedown': 'core:select-page-down'
|
||||
'delete': 'core:delete'
|
||||
'shift-delete': 'core:delete'
|
||||
'pageup': 'core:page-up'
|
||||
|
||||
@@ -37,6 +37,8 @@
|
||||
'shift-down': 'core:select-down'
|
||||
'shift-left': 'core:select-left'
|
||||
'shift-right': 'core:select-right'
|
||||
'shift-pageup': 'core:select-page-up'
|
||||
'shift-pagedown': 'core:select-page-down'
|
||||
'delete': 'core:delete'
|
||||
'shift-delete': 'core:delete'
|
||||
'pageup': 'core:page-up'
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
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' }
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
{ label: 'Add Selection &Above', command: 'editor:add-selection-above' }
|
||||
{ label: 'Add Selection &Below', command: 'editor:add-selection-below' }
|
||||
{ label: 'S&plit into Lines', command: 'editor:split-selections-into-lines'}
|
||||
{ label: 'Single Selection', command: 'editor:consolidate-selections'}
|
||||
{ type: 'separator' }
|
||||
{ label: 'Select to &Top', command: 'core:select-to-top' }
|
||||
{ label: 'Select to Botto&m', command: 'core:select-to-bottom' }
|
||||
|
||||
@@ -127,6 +127,7 @@
|
||||
{ label: 'Add Selection &Above', command: 'editor:add-selection-above' }
|
||||
{ label: 'Add Selection &Below', command: 'editor:add-selection-below' }
|
||||
{ label: 'S&plit into Lines', command: 'editor:split-selections-into-lines'}
|
||||
{ label: 'Single Selection', command: 'editor:consolidate-selections'}
|
||||
{ type: 'separator' }
|
||||
{ label: 'Select to &Top', command: 'core:select-to-top' }
|
||||
{ label: 'Select to Botto&m', command: 'core:select-to-bottom' }
|
||||
|
||||
+31
-31
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "atom",
|
||||
"productName": "Atom",
|
||||
"version": "0.104.0",
|
||||
"version": "0.106.0",
|
||||
"description": "A hackable text editor for the 21st Century.",
|
||||
"main": "./src/browser/main.js",
|
||||
"repository": {
|
||||
@@ -17,7 +17,7 @@
|
||||
"url": "http://github.com/atom/atom/raw/master/LICENSE.md"
|
||||
}
|
||||
],
|
||||
"atomShellVersion": "0.13.1",
|
||||
"atomShellVersion": "0.13.2",
|
||||
"dependencies": {
|
||||
"async": "0.2.6",
|
||||
"atom-keymap": "^0.27.0",
|
||||
@@ -56,67 +56,67 @@
|
||||
"serializable": "^1",
|
||||
"space-pen": "3.2.0",
|
||||
"temp": "0.7.0",
|
||||
"text-buffer": "^2.3.0",
|
||||
"text-buffer": "^2.4.2",
|
||||
"theorist": "^1",
|
||||
"underscore-plus": "^1.4.1",
|
||||
"underscore-plus": "^1.5.0",
|
||||
"vm-compatibility-layer": "0.1.0"
|
||||
},
|
||||
"packageDependencies": {
|
||||
"atom-dark-syntax": "0.16.0",
|
||||
"atom-dark-syntax": "0.17.0",
|
||||
"atom-dark-ui": "0.29.0",
|
||||
"atom-light-syntax": "0.17.0",
|
||||
"atom-light-syntax": "0.18.0",
|
||||
"atom-light-ui": "0.25.0",
|
||||
"base16-tomorrow-dark-theme": "0.16.0",
|
||||
"solarized-dark-syntax": "0.17.0",
|
||||
"solarized-light-syntax": "0.8.0",
|
||||
"archive-view": "0.32.0",
|
||||
"base16-tomorrow-dark-theme": "0.17.0",
|
||||
"solarized-dark-syntax": "0.18.0",
|
||||
"solarized-light-syntax": "0.9.0",
|
||||
"archive-view": "0.33.0",
|
||||
"autocomplete": "0.28.0",
|
||||
"autoflow": "0.17.0",
|
||||
"autosave": "0.14.0",
|
||||
"background-tips": "0.14.0",
|
||||
"bookmarks": "0.24.0",
|
||||
"bracket-matcher": "0.46.0",
|
||||
"command-palette": "0.22.0",
|
||||
"bookmarks": "0.25.0",
|
||||
"bracket-matcher": "0.47.0",
|
||||
"command-palette": "0.23.0",
|
||||
"deprecation-cop": "0.7.0",
|
||||
"dev-live-reload": "0.31.0",
|
||||
"exception-reporting": "0.18.0",
|
||||
"feedback": "0.33.0",
|
||||
"find-and-replace": "0.117.0",
|
||||
"fuzzy-finder": "0.54.0",
|
||||
"git-diff": "0.31.0",
|
||||
"go-to-line": "0.22.0",
|
||||
"find-and-replace": "0.120.0",
|
||||
"fuzzy-finder": "0.55.0",
|
||||
"git-diff": "0.34.0",
|
||||
"go-to-line": "0.23.0",
|
||||
"grammar-selector": "0.27.0",
|
||||
"image-view": "0.35.0",
|
||||
"keybinding-resolver": "0.18.0",
|
||||
"link": "0.22.0",
|
||||
"markdown-preview": "0.80.0",
|
||||
"link": "0.24.0",
|
||||
"markdown-preview": "0.82.0",
|
||||
"metrics": "0.32.0",
|
||||
"open-on-github": "0.28.0",
|
||||
"package-generator": "0.31.0",
|
||||
"release-notes": "0.32.0",
|
||||
"settings-view": "0.127.0",
|
||||
"snippets": "0.45.0",
|
||||
"spell-check": "0.37.0",
|
||||
"status-bar": "0.40.0",
|
||||
"settings-view": "0.128.0",
|
||||
"snippets": "0.46.0",
|
||||
"spell-check": "0.38.0",
|
||||
"status-bar": "0.41.0",
|
||||
"styleguide": "0.29.0",
|
||||
"symbols-view": "0.56.0",
|
||||
"tabs": "0.41.0",
|
||||
"tabs": "0.42.0",
|
||||
"timecop": "0.19.0",
|
||||
"tree-view": "0.103.0",
|
||||
"tree-view": "0.104.0",
|
||||
"update-package-dependencies": "0.6.0",
|
||||
"welcome": "0.16.0",
|
||||
"whitespace": "0.22.0",
|
||||
"wrap-guide": "0.18.0",
|
||||
"language-c": "0.18.0",
|
||||
"wrap-guide": "0.19.0",
|
||||
"language-c": "0.21.0",
|
||||
"language-coffee-script": "0.22.0",
|
||||
"language-css": "0.17.0",
|
||||
"language-gfm": "0.39.0",
|
||||
"language-gfm": "0.40.0",
|
||||
"language-git": "0.9.0",
|
||||
"language-go": "0.12.0",
|
||||
"language-html": "0.22.0",
|
||||
"language-hyperlink": "0.10.0",
|
||||
"language-java": "0.10.0",
|
||||
"language-javascript": "0.27.0",
|
||||
"language-javascript": "0.28.0",
|
||||
"language-json": "0.8.0",
|
||||
"language-less": "0.9.0",
|
||||
"language-make": "0.10.0",
|
||||
@@ -125,7 +125,7 @@
|
||||
"language-php": "0.15.0",
|
||||
"language-property-list": "0.7.0",
|
||||
"language-python": "0.18.0",
|
||||
"language-ruby": "0.29.0",
|
||||
"language-ruby": "0.30.0",
|
||||
"language-ruby-on-rails": "0.14.0",
|
||||
"language-sass": "0.13.0",
|
||||
"language-shellscript": "0.8.0",
|
||||
@@ -134,7 +134,7 @@
|
||||
"language-text": "0.6.0",
|
||||
"language-todo": "0.10.0",
|
||||
"language-toml": "0.12.0",
|
||||
"language-xml": "0.14.0",
|
||||
"language-xml": "0.15.0",
|
||||
"language-yaml": "0.7.0"
|
||||
},
|
||||
"private": true,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[Desktop Entry]
|
||||
Name=Atom
|
||||
Comment=<%= description %>
|
||||
Exec=/usr/share/atom/atom %U
|
||||
Icon=atom
|
||||
Exec=<%= installDir %>/share/atom/atom %U
|
||||
Icon=<%= iconName %>
|
||||
Type=Application
|
||||
StartupNotify=true
|
||||
Categories=GNOME;GTK;Utility;TextEditor;
|
||||
|
||||
Arquivo binário não exibido.
Arquivo binário não exibido.
@@ -912,6 +912,42 @@ describe "DisplayBuffer", ->
|
||||
expect(displayBuffer.findMarkers(class: 'a', startBufferRow: 0, endBufferRow: 3)).toEqual [marker1]
|
||||
expect(displayBuffer.findMarkers(endBufferRow: 10)).toEqual [marker3]
|
||||
|
||||
it "allows the startScreenRow and endScreenRow to be specified", ->
|
||||
marker1 = displayBuffer.markBufferRange([[6, 0], [7, 0]], class: 'a')
|
||||
marker2 = displayBuffer.markBufferRange([[9, 0], [10, 0]], class: 'a')
|
||||
displayBuffer.createFold(4, 7)
|
||||
expect(displayBuffer.findMarkers(class: 'a', startScreenRow: 6, endScreenRow: 7)).toEqual [marker2]
|
||||
|
||||
it "allows intersectsBufferRowRange to be specified", ->
|
||||
marker1 = displayBuffer.markBufferRange([[5, 0], [5, 0]], class: 'a')
|
||||
marker2 = displayBuffer.markBufferRange([[8, 0], [8, 0]], class: 'a')
|
||||
displayBuffer.createFold(4, 7)
|
||||
expect(displayBuffer.findMarkers(class: 'a', intersectsBufferRowRange: [5, 6])).toEqual [marker1]
|
||||
|
||||
it "allows intersectsScreenRowRange to be specified", ->
|
||||
marker1 = displayBuffer.markBufferRange([[5, 0], [5, 0]], class: 'a')
|
||||
marker2 = displayBuffer.markBufferRange([[8, 0], [8, 0]], class: 'a')
|
||||
displayBuffer.createFold(4, 7)
|
||||
expect(displayBuffer.findMarkers(class: 'a', intersectsScreenRowRange: [5, 10])).toEqual [marker2]
|
||||
|
||||
it "allows containedInScreenRange to be specified", ->
|
||||
marker1 = displayBuffer.markBufferRange([[5, 0], [5, 0]], class: 'a')
|
||||
marker2 = displayBuffer.markBufferRange([[8, 0], [8, 0]], class: 'a')
|
||||
displayBuffer.createFold(4, 7)
|
||||
expect(displayBuffer.findMarkers(class: 'a', containedInScreenRange: [[5, 0], [7, 0]])).toEqual [marker2]
|
||||
|
||||
it "allows intersectsBufferRange to be specified", ->
|
||||
marker1 = displayBuffer.markBufferRange([[5, 0], [5, 0]], class: 'a')
|
||||
marker2 = displayBuffer.markBufferRange([[8, 0], [8, 0]], class: 'a')
|
||||
displayBuffer.createFold(4, 7)
|
||||
expect(displayBuffer.findMarkers(class: 'a', intersectsBufferRange: [[5, 0], [6, 0]])).toEqual [marker1]
|
||||
|
||||
it "allows intersectsScreenRange to be specified", ->
|
||||
marker1 = displayBuffer.markBufferRange([[5, 0], [5, 0]], class: 'a')
|
||||
marker2 = displayBuffer.markBufferRange([[8, 0], [8, 0]], class: 'a')
|
||||
displayBuffer.createFold(4, 7)
|
||||
expect(displayBuffer.findMarkers(class: 'a', intersectsScreenRange: [[5, 0], [10, 0]])).toEqual [marker2]
|
||||
|
||||
describe "marker destruction", ->
|
||||
it "allows markers to be destroyed", ->
|
||||
marker = displayBuffer.markScreenRange([[5, 4], [5, 10]])
|
||||
@@ -956,6 +992,17 @@ describe "DisplayBuffer", ->
|
||||
expect(start.top).toBe 5 * 20
|
||||
expect(start.left).toBe (4 * 10) + (6 * 11)
|
||||
|
||||
describe "decorations", ->
|
||||
it "can add decorations associated with markers and remove them", ->
|
||||
decoration = {type: 'gutter', class: 'one'}
|
||||
marker = displayBuffer.markBufferRange([[2, 13], [3, 15]])
|
||||
|
||||
displayBuffer.addDecorationForMarker(marker, decoration)
|
||||
expect(displayBuffer.decorationsForScreenRowRange(2, 3)[marker.id][0]).toBe decoration
|
||||
|
||||
displayBuffer.removeDecorationForMarker(marker, decoration)
|
||||
expect(displayBuffer.decorationsForScreenRowRange(2, 3)[marker.id]).not.toBeDefined()
|
||||
|
||||
describe "::setScrollTop", ->
|
||||
beforeEach ->
|
||||
displayBuffer.manageScrollPosition = true
|
||||
@@ -997,17 +1044,26 @@ describe "DisplayBuffer", ->
|
||||
expect(displayBuffer.setScrollLeft(maxScrollLeft + 50)).toBe maxScrollLeft
|
||||
expect(displayBuffer.getScrollLeft()).toBe maxScrollLeft
|
||||
|
||||
describe "::scrollToScreenPosition(position)", ->
|
||||
it "sets the scroll top and scroll left so the given screen position is in view", ->
|
||||
describe "::scrollToScreenPosition(position, [options])", ->
|
||||
beforeEach ->
|
||||
displayBuffer.manageScrollPosition = true
|
||||
displayBuffer.setLineHeightInPixels(10)
|
||||
displayBuffer.setDefaultCharWidth(10)
|
||||
displayBuffer.setHorizontalScrollbarHeight(0)
|
||||
|
||||
displayBuffer.setHeight(50)
|
||||
displayBuffer.setWidth(50)
|
||||
maxScrollTop = displayBuffer.getScrollHeight() - displayBuffer.getHeight()
|
||||
|
||||
it "sets the scroll top and scroll left so the given screen position is in view", ->
|
||||
displayBuffer.scrollToScreenPosition([8, 20])
|
||||
expect(displayBuffer.getScrollBottom()).toBe (9 + displayBuffer.getVerticalScrollMargin()) * 10
|
||||
expect(displayBuffer.getScrollRight()).toBe (20 + displayBuffer.getHorizontalScrollMargin()) * 10
|
||||
|
||||
describe "when the 'center' option is true", ->
|
||||
it "vertically scrolls to center the given position vertically", ->
|
||||
displayBuffer.scrollToScreenPosition([8, 20], center: true)
|
||||
expect(displayBuffer.getScrollTop()).toBe (8 * 10) + 5 - (50 / 2)
|
||||
expect(displayBuffer.getScrollRight()).toBe (20 + displayBuffer.getHorizontalScrollMargin()) * 10
|
||||
|
||||
it "does not scroll vertically if the position is already in view", ->
|
||||
displayBuffer.scrollToScreenPosition([4, 20], center: true)
|
||||
expect(displayBuffer.getScrollTop()).toBe 0
|
||||
|
||||
+563
-146
@@ -19,7 +19,7 @@ describe "EditorComponent", ->
|
||||
spyOn(window, "clearInterval").andCallFake window.fakeClearInterval
|
||||
|
||||
delayAnimationFrames = false
|
||||
nextAnimationFrame = null
|
||||
nextAnimationFrame = -> throw new Error('No animation frame requested')
|
||||
spyOn(window, 'requestAnimationFrame').andCallFake (fn) ->
|
||||
if delayAnimationFrames
|
||||
nextAnimationFrame = fn
|
||||
@@ -143,15 +143,15 @@ describe "EditorComponent", ->
|
||||
it "re-renders the lines when the showInvisibles config option changes", ->
|
||||
editor.setText " a line with tabs\tand spaces "
|
||||
|
||||
expect(component.lineNodeForScreenRow(0).textContent).toBe "#{invisibles.space}a line with tabs#{invisibles.tab} and spaces#{invisibles.space}#{invisibles.eol}"
|
||||
expect(component.lineNodeForScreenRow(0).textContent).toBe "#{invisibles.space}a line with tabs#{invisibles.tab}and spaces#{invisibles.space}#{invisibles.eol}"
|
||||
atom.config.set("editor.showInvisibles", false)
|
||||
expect(component.lineNodeForScreenRow(0).textContent).toBe " a line with tabs and spaces "
|
||||
expect(component.lineNodeForScreenRow(0).textContent).toBe " a line with tabs and spaces "
|
||||
atom.config.set("editor.showInvisibles", true)
|
||||
expect(component.lineNodeForScreenRow(0).textContent).toBe "#{invisibles.space}a line with tabs#{invisibles.tab} and spaces#{invisibles.space}#{invisibles.eol}"
|
||||
expect(component.lineNodeForScreenRow(0).textContent).toBe "#{invisibles.space}a line with tabs#{invisibles.tab}and spaces#{invisibles.space}#{invisibles.eol}"
|
||||
|
||||
it "displays spaces, tabs, and newlines as visible characters", ->
|
||||
editor.setText " a line with tabs\tand spaces "
|
||||
expect(component.lineNodeForScreenRow(0).textContent).toBe "#{invisibles.space}a line with tabs#{invisibles.tab} and spaces#{invisibles.space}#{invisibles.eol}"
|
||||
expect(component.lineNodeForScreenRow(0).textContent).toBe "#{invisibles.space}a line with tabs#{invisibles.tab}and spaces#{invisibles.space}#{invisibles.eol}"
|
||||
|
||||
it "displays newlines as their own token outside of the other tokens' scopes", ->
|
||||
editor.setText "var"
|
||||
@@ -232,13 +232,113 @@ describe "EditorComponent", ->
|
||||
editor.setText("a\0b")
|
||||
expect(editor.pixelPositionForScreenPosition([0, Infinity]).left).toEqual 2 * charWidth
|
||||
|
||||
describe "when there is a fold", ->
|
||||
it "renders a fold marker on the folded line", ->
|
||||
foldedLineNode = component.lineNodeForScreenRow(4)
|
||||
expect(foldedLineNode.querySelector('.fold-marker')).toBeFalsy()
|
||||
|
||||
editor.foldBufferRow(4)
|
||||
foldedLineNode = component.lineNodeForScreenRow(4)
|
||||
expect(foldedLineNode.querySelector('.fold-marker')).toBeTruthy()
|
||||
|
||||
editor.unfoldBufferRow(4)
|
||||
foldedLineNode = component.lineNodeForScreenRow(4)
|
||||
expect(foldedLineNode.querySelector('.fold-marker')).toBeFalsy()
|
||||
|
||||
describe "when line decorations are attached to markers", ->
|
||||
{marker, decoration} = {}
|
||||
|
||||
lineHasClass = (screenRow, klass) ->
|
||||
component.lineNodeForScreenRow(screenRow).classList.contains(klass)
|
||||
|
||||
beforeEach ->
|
||||
marker = editor.displayBuffer.markBufferRange([[2, 13], [3, 15]], invalidate: 'inside')
|
||||
decoration = {type: 'line', class: 'someclass'}
|
||||
editor.addDecorationForMarker(marker, decoration)
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
|
||||
it "does not render off-screen lines with line number classes until they are with in the rendered row range", ->
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
component.measureScrollView()
|
||||
|
||||
expect(component.lineNodeForScreenRow(9)).not.toBeDefined()
|
||||
|
||||
marker = editor.displayBuffer.markBufferRange([[9, 0], [9, 0]], invalidate: 'inside')
|
||||
editor.addDecorationForMarker(marker, type: 'line', class: 'fancy-class')
|
||||
editor.addDecorationForMarker(marker, type: 'gutter', class: 'nope-class')
|
||||
|
||||
verticalScrollbarNode.scrollTop = 2.5 * lineHeightInPixels
|
||||
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
|
||||
|
||||
expect(lineHasClass(9, 'fancy-class')).toBe true
|
||||
expect(lineHasClass(9, 'nope-class')).toBe false
|
||||
|
||||
it "renders the specified decoration class on the correct lines", ->
|
||||
expect(lineHasClass(1, 'someclass')).toBe false
|
||||
expect(lineHasClass(2, 'someclass')).toBe true
|
||||
expect(lineHasClass(3, 'someclass')).toBe true
|
||||
expect(lineHasClass(4, 'someclass')).toBe false
|
||||
|
||||
it "removes line classes when a decoration's marker is invalidated", ->
|
||||
editor.getBuffer().insert([3, 2], 'n')
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(marker.isValid()).toBe false
|
||||
expect(lineHasClass(1, 'someclass')).toBe false
|
||||
expect(lineHasClass(2, 'someclass')).toBe false
|
||||
expect(lineHasClass(3, 'someclass')).toBe false
|
||||
expect(lineHasClass(4, 'someclass')).toBe false
|
||||
|
||||
editor.getBuffer().undo()
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(marker.isValid()).toBe true
|
||||
expect(lineHasClass(1, 'someclass')).toBe false
|
||||
expect(lineHasClass(2, 'someclass')).toBe true
|
||||
expect(lineHasClass(3, 'someclass')).toBe true
|
||||
expect(lineHasClass(4, 'someclass')).toBe false
|
||||
|
||||
it "removes the classes and unsubscribes from the marker when decoration is removed", ->
|
||||
editor.removeDecorationForMarker(marker, decoration)
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineHasClass(1, 'someclass')).toBe false
|
||||
expect(lineHasClass(2, 'someclass')).toBe false
|
||||
expect(lineHasClass(3, 'someclass')).toBe false
|
||||
expect(lineHasClass(4, 'someclass')).toBe false
|
||||
|
||||
editor.getBuffer().insert([0, 0], '\n')
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineHasClass(2, 'someclass')).toBe false
|
||||
expect(lineHasClass(3, 'someclass')).toBe false
|
||||
|
||||
it "removes the line number classes when the decoration's marker is destroyed", ->
|
||||
marker.destroy()
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineHasClass(1, 'someclass')).toBe false
|
||||
expect(lineHasClass(2, 'someclass')).toBe false
|
||||
expect(lineHasClass(3, 'someclass')).toBe false
|
||||
expect(lineHasClass(4, 'someclass')).toBe false
|
||||
|
||||
describe "gutter rendering", ->
|
||||
[lineNumberHasClass, gutter] = []
|
||||
[gutter] = []
|
||||
|
||||
lineNumberHasClass = (screenRow, klass) ->
|
||||
component.lineNumberNodeForScreenRow(screenRow).classList.contains(klass)
|
||||
|
||||
lineNumberForBufferRowHasClass = (bufferRow, klass) ->
|
||||
screenRow = editor.displayBuffer.screenRowForBufferRow(bufferRow)
|
||||
component.lineNumberNodeForScreenRow(screenRow).classList.contains(klass)
|
||||
|
||||
beforeEach ->
|
||||
{gutter} = component.refs
|
||||
lineNumberHasClass = (screenRow, klass) ->
|
||||
component.lineNumberNodeForScreenRow(screenRow).classList.contains(klass)
|
||||
|
||||
it "renders the currently-visible line numbers", ->
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
@@ -314,6 +414,11 @@ describe "EditorComponent", ->
|
||||
expect(component.lineNumberNodeForScreenRow(9).textContent).toBe "10"
|
||||
expect(gutterNode.offsetWidth).toBe initialGutterWidth
|
||||
|
||||
it "renders the .line-numbers div at the full height of the editor even if it's taller than its content", ->
|
||||
node.style.height = node.offsetHeight + 100 + 'px'
|
||||
component.measureScrollView()
|
||||
expect(node.querySelector('.line-numbers').offsetHeight).toBe node.offsetHeight
|
||||
|
||||
describe "fold decorations", ->
|
||||
describe "rendering fold decorations", ->
|
||||
it "adds the foldable class to line numbers when the line is foldable", ->
|
||||
@@ -416,7 +521,7 @@ describe "EditorComponent", ->
|
||||
expect(lineNumberHasClass(10, 'cursor-line')).toBe false
|
||||
|
||||
it "adds cursor-line decorations to multiple lines when a selection is performed", ->
|
||||
cursor.setScreenPosition([1, 0])
|
||||
cursor.setScreenPosition([1, 5])
|
||||
editor.selectDown(2)
|
||||
expect(lineNumberHasClass(0, 'cursor-line')).toBe false
|
||||
expect(lineNumberHasClass(1, 'cursor-line')).toBe true
|
||||
@@ -424,156 +529,149 @@ describe "EditorComponent", ->
|
||||
expect(lineNumberHasClass(3, 'cursor-line')).toBe true
|
||||
expect(lineNumberHasClass(4, 'cursor-line')).toBe false
|
||||
|
||||
describe "when decorations are used", ->
|
||||
describe "when decorations are applied to buffer rows", ->
|
||||
it "renders line number classes based on the decorations on their buffer row", ->
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
component.measureScrollView()
|
||||
it "does not render a cursor-line decoration for the last line of a multi-line selection of the selection ends at column 0", ->
|
||||
cursor.setScreenPosition([1, 0])
|
||||
editor.selectDown(2)
|
||||
expect(lineNumberHasClass(0, 'cursor-line')).toBe false
|
||||
expect(lineNumberHasClass(1, 'cursor-line')).toBe true
|
||||
expect(lineNumberHasClass(2, 'cursor-line')).toBe true
|
||||
expect(lineNumberHasClass(3, 'cursor-line')).toBe false
|
||||
|
||||
expect(component.lineNumberNodeForScreenRow(9)).not.toBeDefined()
|
||||
describe "when gutter decorations are attached to markers", ->
|
||||
{marker, decoration} = {}
|
||||
beforeEach ->
|
||||
marker = editor.displayBuffer.markBufferRange([[2, 13], [3, 15]], invalidate: 'inside')
|
||||
decoration = {type: 'gutter', class: 'someclass'}
|
||||
editor.addDecorationForMarker(marker, decoration)
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
|
||||
editor.addDecorationToBufferRow(9, type: 'gutter', class: 'fancy-class')
|
||||
editor.addDecorationToBufferRow(9, type: 'someother-type', class: 'nope-class')
|
||||
it "does not render off-screen lines with line number classes until they are with in the rendered row range", ->
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
component.measureScrollView()
|
||||
|
||||
verticalScrollbarNode.scrollTop = 2.5 * lineHeightInPixels
|
||||
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
|
||||
expect(component.lineNumberNodeForScreenRow(9)).not.toBeDefined()
|
||||
|
||||
expect(lineNumberHasClass(9, 'fancy-class')).toBe true
|
||||
expect(lineNumberHasClass(9, 'nope-class')).toBe false
|
||||
marker = editor.displayBuffer.markBufferRange([[9, 0], [9, 0]], invalidate: 'inside')
|
||||
editor.addDecorationForMarker(marker, type: 'gutter', class: 'fancy-class')
|
||||
editor.addDecorationForMarker(marker, type: 'someother-type', class: 'nope-class')
|
||||
|
||||
it "renders updates to gutter decorations", ->
|
||||
editor.addDecorationToBufferRow(2, type: 'gutter', class: 'fancy-class')
|
||||
editor.addDecorationToBufferRow(2, type: 'someother-type', class: 'nope-class')
|
||||
verticalScrollbarNode.scrollTop = 2.5 * lineHeightInPixels
|
||||
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(2, 'fancy-class')).toBe true
|
||||
expect(lineNumberHasClass(2, 'nope-class')).toBe false
|
||||
expect(lineNumberHasClass(9, 'fancy-class')).toBe true
|
||||
expect(lineNumberHasClass(9, 'nope-class')).toBe false
|
||||
|
||||
editor.removeDecorationFromBufferRow(2, type: 'gutter', class: 'fancy-class')
|
||||
editor.removeDecorationFromBufferRow(2, type: 'someother-type', class: 'nope-class')
|
||||
it "renders classes on correct screen lines when the user folds a block of code", ->
|
||||
marker = editor.displayBuffer.markBufferRange([[9, 0], [9, 0]], invalidate: 'inside')
|
||||
editor.addDecorationForMarker(marker, decoration)
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(2, 'fancy-class')).toBe false
|
||||
expect(lineNumberHasClass(2, 'nope-class')).toBe false
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberForBufferRowHasClass(9, 'someclass')).toBe true
|
||||
editor.foldBufferRow(5)
|
||||
editor.removeDecorationForMarker(marker, decoration)
|
||||
|
||||
it "renders decorations on soft-wrapped line numbers when softWrap is true", ->
|
||||
editor.addDecorationToBufferRow(1, type: 'gutter', class: 'no-wrap')
|
||||
editor.addDecorationToBufferRow(1, type: 'gutter', class: 'wrap-me', softWrap: true)
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberForBufferRowHasClass(9, 'someclass')).toBe false
|
||||
|
||||
editor.setSoftWrap(true)
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
node.style.width = 30 * charWidth + 'px'
|
||||
component.measureScrollView()
|
||||
it "updates line number classes when the marker moves", ->
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe false
|
||||
|
||||
expect(lineNumberHasClass(2, 'no-wrap')).toBe true
|
||||
expect(lineNumberHasClass(2, 'wrap-me')).toBe true
|
||||
expect(lineNumberHasClass(3, 'no-wrap')).toBe false
|
||||
expect(lineNumberHasClass(3, 'wrap-me')).toBe true
|
||||
editor.getBuffer().insert([0, 0], '\n')
|
||||
|
||||
# should remove the wrapped decorations
|
||||
editor.removeDecorationFromBufferRow(1, type: 'gutter', class: 'no-wrap')
|
||||
editor.removeDecorationFromBufferRow(1, type: 'gutter', class: 'wrap-me')
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(5, 'someclass')).toBe false
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(2, 'no-wrap')).toBe false
|
||||
expect(lineNumberHasClass(2, 'wrap-me')).toBe false
|
||||
expect(lineNumberHasClass(3, 'no-wrap')).toBe false
|
||||
expect(lineNumberHasClass(3, 'wrap-me')).toBe false
|
||||
editor.getBuffer().deleteRows(0, 1)
|
||||
|
||||
# should add them back when the nodes are not recreated
|
||||
editor.addDecorationToBufferRow(1, type: 'gutter', class: 'no-wrap')
|
||||
editor.addDecorationToBufferRow(1, type: 'gutter', class: 'wrap-me', softWrap: true)
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(0, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe false
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(2, 'no-wrap')).toBe true
|
||||
expect(lineNumberHasClass(2, 'wrap-me')).toBe true
|
||||
expect(lineNumberHasClass(3, 'no-wrap')).toBe false
|
||||
expect(lineNumberHasClass(3, 'wrap-me')).toBe true
|
||||
it "removes line number classes when a decoration's marker is invalidated", ->
|
||||
editor.getBuffer().insert([3, 2], 'n')
|
||||
|
||||
describe "when decorations are applied to markers", ->
|
||||
{marker, decoration} = {}
|
||||
beforeEach ->
|
||||
marker = editor.displayBuffer.markBufferRange([[2, 13], [3, 15]], class: 'my-marker', invalidate: 'inside')
|
||||
decoration = {type: 'gutter', class: 'someclass'}
|
||||
editor.addDecorationForMarker(marker, decoration)
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
|
||||
it "updates line number classes when the marker moves", ->
|
||||
expect(marker.isValid()).toBe false
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe false
|
||||
|
||||
editor.getBuffer().undo()
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(marker.isValid()).toBe true
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe false
|
||||
|
||||
editor.getBuffer().insert([0, 0], '\n')
|
||||
it "removes the classes and unsubscribes from the marker when decoration is removed", ->
|
||||
editor.removeDecorationForMarker(marker, decoration)
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe false
|
||||
|
||||
editor.getBuffer().insert([0, 0], '\n')
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe false
|
||||
|
||||
it "removes the line number classes when the decoration's marker is destroyed", ->
|
||||
marker.destroy()
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe false
|
||||
|
||||
describe "when soft wrapping is enabled", ->
|
||||
beforeEach ->
|
||||
editor.setText "a line that wraps, ok"
|
||||
editor.setSoftWrap(true)
|
||||
node.style.width = 16 * charWidth + 'px'
|
||||
component.measureScrollView()
|
||||
|
||||
it "applies decoration only to the first row when marker range does not wrap", ->
|
||||
marker = editor.displayBuffer.markBufferRange([[0, 0], [0, 0]])
|
||||
editor.addDecorationForMarker(marker, type: 'gutter', class: 'someclass')
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(5, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(0, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe false
|
||||
|
||||
editor.getBuffer().deleteRows(0, 1)
|
||||
it "applies decoration to both rows when marker wraps", ->
|
||||
marker = editor.displayBuffer.markBufferRange([[0, 0], [0, Infinity]])
|
||||
editor.addDecorationForMarker(marker, type: 'gutter', class: 'someclass')
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(0, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(0, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe false
|
||||
|
||||
it "removes line number classes when a decoration's marker is invalidated", ->
|
||||
editor.getBuffer().insert([3, 2], 'n')
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
|
||||
expect(marker.isValid()).toBe false
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe false
|
||||
|
||||
editor.getBuffer().undo()
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(marker.isValid()).toBe true
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe false
|
||||
|
||||
it "removes the classes and unsubscribes from the marker when decoration is removed", ->
|
||||
editor.removeDecorationForMarker(marker, decoration)
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe false
|
||||
|
||||
editor.getBuffer().insert([0, 0], '\n')
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe false
|
||||
|
||||
it "removes the line number classes when the decoration's marker is destroyed", ->
|
||||
marker.destroy()
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe false
|
||||
|
||||
describe "cursor rendering", ->
|
||||
it "renders the currently visible cursors, translated relative to the scroll position", ->
|
||||
@@ -746,12 +844,12 @@ describe "EditorComponent", ->
|
||||
expect(region3Rect.left).toBe scrollViewClientLeft + 0
|
||||
expect(region3Rect.width).toBe 10 * charWidth
|
||||
|
||||
it "does not render empty selections unless they are the first selection (to prevent a Chromium rendering artifact caused by removing it)", ->
|
||||
it "does not render empty selections", ->
|
||||
editor.addSelectionForBufferRange([[2, 2], [2, 2]])
|
||||
expect(editor.getSelection(0).isEmpty()).toBe true
|
||||
expect(editor.getSelection(1).isEmpty()).toBe true
|
||||
|
||||
expect(node.querySelectorAll('.selection').length).toBe 1
|
||||
expect(node.querySelectorAll('.selection').length).toBe 0
|
||||
|
||||
it "updates selections when the line height changes", ->
|
||||
editor.setSelectedBufferRange([[1, 6], [1, 10]])
|
||||
@@ -773,6 +871,99 @@ describe "EditorComponent", ->
|
||||
expect(selectionNode.offsetTop).toBe editor.getLineHeightInPixels()
|
||||
expect(selectionNode.offsetLeft).toBe editor.pixelPositionForScreenPosition([1, 6]).left
|
||||
|
||||
describe "highlight decoration rendering", ->
|
||||
[marker, decoration, scrollViewClientLeft] = []
|
||||
beforeEach ->
|
||||
scrollViewClientLeft = node.querySelector('.scroll-view').getBoundingClientRect().left
|
||||
marker = editor.displayBuffer.markBufferRange([[2, 13], [3, 15]], invalidate: 'inside')
|
||||
decoration = {type: 'highlight', class: 'test-highlight'}
|
||||
editor.addDecorationForMarker(marker, decoration)
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
|
||||
it "does not render highlights for off-screen lines until they come on-screen", ->
|
||||
node.style.height = 2.5 * lineHeightInPixels + 'px'
|
||||
component.measureScrollView()
|
||||
|
||||
marker = editor.displayBuffer.markBufferRange([[9, 2], [9, 4]], invalidate: 'inside')
|
||||
editor.addDecorationForMarker(marker, type: 'highlight', class: 'some-highlight')
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
# Should not be rendering range containing the marker
|
||||
expect(component.getRenderedRowRange()[1]).toBeLessThan 9
|
||||
|
||||
regions = node.querySelectorAll('.some-highlight .region')
|
||||
|
||||
# Nothing when outside the rendered row range
|
||||
expect(regions.length).toBe 0
|
||||
|
||||
verticalScrollbarNode.scrollTop = 3.5 * lineHeightInPixels
|
||||
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
|
||||
|
||||
regions = node.querySelectorAll('.some-highlight .region')
|
||||
|
||||
expect(regions.length).toBe 1
|
||||
regionRect = regions[0].style
|
||||
expect(regionRect.top).toBe 9 * lineHeightInPixels + 'px'
|
||||
expect(regionRect.height).toBe 1 * lineHeightInPixels + 'px'
|
||||
expect(regionRect.left).toBe 2 * charWidth + 'px'
|
||||
expect(regionRect.width).toBe 2 * charWidth + 'px'
|
||||
|
||||
it "renders highlights decoration's marker is added", ->
|
||||
regions = node.querySelectorAll('.test-highlight .region')
|
||||
expect(regions.length).toBe 2
|
||||
|
||||
it "removes highlights when a decoration is removed", ->
|
||||
editor.removeDecorationForMarker(marker, decoration)
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
regions = node.querySelectorAll('.test-highlight .region')
|
||||
expect(regions.length).toBe 0
|
||||
|
||||
it "does not render a highlight that is within a fold", ->
|
||||
editor.foldBufferRow(1)
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(node.querySelectorAll('.test-highlight').length).toBe 0
|
||||
|
||||
it "moves rendered highlights when the marker moves", ->
|
||||
regionStyle = node.querySelector('.test-highlight .region').style
|
||||
originalTop = parseInt(regionStyle.top)
|
||||
|
||||
editor.getBuffer().insert([0, 0], '\n')
|
||||
|
||||
regionStyle = node.querySelector('.test-highlight .region').style
|
||||
newTop = parseInt(regionStyle.top)
|
||||
|
||||
expect(newTop).toBe originalTop + lineHeightInPixels
|
||||
|
||||
it "removes highlights when a decoration's marker is destroyed", ->
|
||||
marker.destroy()
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
regions = node.querySelectorAll('.test-highlight .region')
|
||||
expect(regions.length).toBe 0
|
||||
|
||||
it "only renders highlights when a decoration's marker is valid", ->
|
||||
editor.getBuffer().insert([3, 2], 'n')
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(marker.isValid()).toBe false
|
||||
regions = node.querySelectorAll('.test-highlight .region')
|
||||
expect(regions.length).toBe 0
|
||||
|
||||
editor.getBuffer().undo()
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(marker.isValid()).toBe true
|
||||
regions = node.querySelectorAll('.test-highlight .region')
|
||||
expect(regions.length).toBe 2
|
||||
|
||||
describe "hidden input field", ->
|
||||
it "renders the hidden input field at the position of the last cursor if the cursor is on screen and the editor is focused", ->
|
||||
editor.setVerticalScrollMargin(0)
|
||||
@@ -815,7 +1006,7 @@ describe "EditorComponent", ->
|
||||
expect(inputNode.offsetTop).toBe 0
|
||||
expect(inputNode.offsetLeft).toBe 0
|
||||
|
||||
describe "mouse interactions", ->
|
||||
describe "mouse interactions on the scrollView", ->
|
||||
linesNode = null
|
||||
|
||||
beforeEach ->
|
||||
@@ -906,12 +1097,110 @@ describe "EditorComponent", ->
|
||||
nextAnimationFrame()
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[2, 4], [6, 8]]
|
||||
|
||||
clientCoordinatesForScreenPosition = (screenPosition) ->
|
||||
positionOffset = editor.pixelPositionForScreenPosition(screenPosition)
|
||||
scrollViewClientRect = node.querySelector('.scroll-view').getBoundingClientRect()
|
||||
clientX = scrollViewClientRect.left + positionOffset.left - editor.getScrollLeft()
|
||||
clientY = scrollViewClientRect.top + positionOffset.top - editor.getScrollTop()
|
||||
{clientX, clientY}
|
||||
describe "when a line is folded", ->
|
||||
beforeEach ->
|
||||
editor.foldBufferRow 4
|
||||
|
||||
describe "when the folded line's fold-marker is clicked", ->
|
||||
it "unfolds the buffer row", ->
|
||||
target = component.lineNodeForScreenRow(4).querySelector '.fold-marker'
|
||||
linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([4, 8]), {target}))
|
||||
expect(editor.isFoldedAtBufferRow 4).toBe false
|
||||
|
||||
describe "mouse interactions on the gutter", ->
|
||||
gutterNode = null
|
||||
|
||||
beforeEach ->
|
||||
gutterNode = node.querySelector('.gutter')
|
||||
|
||||
describe "when the gutter is clicked", ->
|
||||
it "moves the cursor to the beginning of the clicked row", ->
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(4)))
|
||||
expect(editor.getCursorScreenPosition()).toEqual [4, 0]
|
||||
|
||||
describe "when the gutter is shift-clicked", ->
|
||||
beforeEach ->
|
||||
editor.setSelectedScreenRange([[3, 4], [4, 5]])
|
||||
|
||||
describe "when the clicked row is before the current selection's tail", ->
|
||||
it "selects to the beginning of the clicked row", ->
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(1), shiftKey: true))
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[1, 0], [3, 4]]
|
||||
|
||||
describe "when the clicked row is after the current selection's tail", ->
|
||||
it "selects to the beginning of the row following the clicked row", ->
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(6), shiftKey: true))
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[3, 4], [7, 0]]
|
||||
|
||||
describe "when the gutter is clicked and dragged", ->
|
||||
beforeEach ->
|
||||
delayAnimationFrames = true
|
||||
|
||||
describe "when dragging downward", ->
|
||||
it "selects the rows between the start and end of the drag", ->
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(2)))
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(6)))
|
||||
nextAnimationFrame()
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(6)))
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[2, 0], [7, 0]]
|
||||
|
||||
describe "when dragging upward", ->
|
||||
it "selects the rows between the start and end of the drag", ->
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(6)))
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(2)))
|
||||
nextAnimationFrame()
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenRowInGutter(2)))
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[2, 0], [7, 0]]
|
||||
|
||||
describe "when the gutter is shift-clicked and dragged", ->
|
||||
beforeEach ->
|
||||
delayAnimationFrames = true
|
||||
|
||||
describe "when the shift-click is below the existing selection's tail", ->
|
||||
describe "when dragging downward", ->
|
||||
it "selects the rows between the existing selection's tail and the end of the drag", ->
|
||||
editor.setSelectedScreenRange([[3, 4], [4, 5]])
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(7), shiftKey: true))
|
||||
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(8)))
|
||||
nextAnimationFrame()
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[3, 4], [9, 0]]
|
||||
|
||||
describe "when dragging upward", ->
|
||||
it "selects the rows between the end of the drag and the tail of the existing selection", ->
|
||||
editor.setSelectedScreenRange([[4, 4], [5, 5]])
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(7), shiftKey: true))
|
||||
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(5)))
|
||||
nextAnimationFrame()
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[4, 4], [6, 0]]
|
||||
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(1)))
|
||||
nextAnimationFrame()
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[1, 0], [4, 4]]
|
||||
|
||||
describe "when the shift-click is above the existing selection's tail", ->
|
||||
describe "when dragging upward", ->
|
||||
it "selects the rows between the end of the drag and the tail of the existing selection", ->
|
||||
editor.setSelectedScreenRange([[4, 4], [5, 5]])
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(2), shiftKey: true))
|
||||
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(1)))
|
||||
nextAnimationFrame()
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[1, 0], [4, 4]]
|
||||
|
||||
describe "when dragging downward", ->
|
||||
it "selects the rows between the existing selection's tail and the end of the drag", ->
|
||||
editor.setSelectedScreenRange([[3, 4], [4, 5]])
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(1), shiftKey: true))
|
||||
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(2)))
|
||||
nextAnimationFrame()
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[2, 0], [3, 4]]
|
||||
|
||||
gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(8)))
|
||||
nextAnimationFrame()
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[3, 4], [9, 0]]
|
||||
|
||||
describe "focus handling", ->
|
||||
inputNode = null
|
||||
@@ -1226,6 +1515,64 @@ describe "EditorComponent", ->
|
||||
node.dispatchEvent(buildTextInputEvent(data: 'x', target: inputNode))
|
||||
expect(editor.lineForBufferRow(0)).toBe 'var quicksort = function () {'
|
||||
|
||||
describe "when IME composition is used to insert international characters", ->
|
||||
buildIMECompositionEvent = (event, {data}={}) ->
|
||||
event = new Event(event)
|
||||
event.data = data
|
||||
Object.defineProperty(event, 'target', get: -> inputNpde)
|
||||
event
|
||||
|
||||
describe "when nothing is selected", ->
|
||||
it "inserts the chosen completion", ->
|
||||
node.dispatchEvent(buildIMECompositionEvent('compositionstart'))
|
||||
node.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 's'))
|
||||
expect(editor.lineForBufferRow(0)).toBe 'svar quicksort = function () {'
|
||||
|
||||
node.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 'sd'))
|
||||
expect(editor.lineForBufferRow(0)).toBe 'sdvar quicksort = function () {'
|
||||
|
||||
node.dispatchEvent(buildIMECompositionEvent('compositionend'))
|
||||
node.dispatchEvent(buildTextInputEvent(data: '速度', target: inputNode))
|
||||
expect(editor.lineForBufferRow(0)).toBe '速度var quicksort = function () {'
|
||||
|
||||
it "reverts back to the original text when the completion helper is dismissed", ->
|
||||
node.dispatchEvent(buildIMECompositionEvent('compositionstart'))
|
||||
node.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 's'))
|
||||
expect(editor.lineForBufferRow(0)).toBe 'svar quicksort = function () {'
|
||||
|
||||
node.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 'sd'))
|
||||
expect(editor.lineForBufferRow(0)).toBe 'sdvar quicksort = function () {'
|
||||
|
||||
node.dispatchEvent(buildIMECompositionEvent('compositionend'))
|
||||
expect(editor.lineForBufferRow(0)).toBe 'var quicksort = function () {'
|
||||
|
||||
describe "when a string is selected", ->
|
||||
beforeEach ->
|
||||
editor.setSelectedBufferRange [[0, 4], [0, 9]] # select 'quick'
|
||||
|
||||
it "inserts the chosen completion", ->
|
||||
node.dispatchEvent(buildIMECompositionEvent('compositionstart'))
|
||||
node.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 's'))
|
||||
expect(editor.lineForBufferRow(0)).toBe 'var ssort = function () {'
|
||||
|
||||
node.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 'sd'))
|
||||
expect(editor.lineForBufferRow(0)).toBe 'var sdsort = function () {'
|
||||
|
||||
node.dispatchEvent(buildIMECompositionEvent('compositionend'))
|
||||
node.dispatchEvent(buildTextInputEvent(data: '速度', target: inputNode))
|
||||
expect(editor.lineForBufferRow(0)).toBe 'var 速度sort = function () {'
|
||||
|
||||
it "reverts back to the original text when the completion helper is dismissed", ->
|
||||
node.dispatchEvent(buildIMECompositionEvent('compositionstart'))
|
||||
node.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 's'))
|
||||
expect(editor.lineForBufferRow(0)).toBe 'var ssort = function () {'
|
||||
|
||||
node.dispatchEvent(buildIMECompositionEvent('compositionupdate', data: 'sd'))
|
||||
expect(editor.lineForBufferRow(0)).toBe 'var sdsort = function () {'
|
||||
|
||||
node.dispatchEvent(buildIMECompositionEvent('compositionend'))
|
||||
expect(editor.lineForBufferRow(0)).toBe 'var quicksort = function () {'
|
||||
|
||||
describe "commands", ->
|
||||
describe "editor:consolidate-selections", ->
|
||||
it "consolidates selections on the editor model, aborting the key binding if there is only one selection", ->
|
||||
@@ -1239,26 +1586,67 @@ describe "EditorComponent", ->
|
||||
expect(event.abortKeyBinding).toHaveBeenCalled()
|
||||
|
||||
describe "hiding and showing the editor", ->
|
||||
describe "when fontSize, fontFamily, or lineHeight changes while the editor is hidden", ->
|
||||
it "does not attempt to measure the lineHeight and defaultCharWidth until the editor becomes visible again", ->
|
||||
describe "when the lineHeight changes while the editor is hidden", ->
|
||||
it "does not attempt to measure the lineHeightInPixels until the editor becomes visible again", ->
|
||||
wrapperView.hide()
|
||||
initialLineHeightInPixels = editor.getLineHeightInPixels()
|
||||
|
||||
component.setLineHeight(2)
|
||||
expect(editor.getLineHeightInPixels()).toBe initialLineHeightInPixels
|
||||
|
||||
wrapperView.show()
|
||||
expect(editor.getLineHeightInPixels()).not.toBe initialLineHeightInPixels
|
||||
|
||||
describe "when the fontSize changes while the editor is hidden", ->
|
||||
it "does not attempt to measure the lineHeightInPixels or defaultCharWidth until the editor becomes visible again", ->
|
||||
wrapperView.hide()
|
||||
initialLineHeightInPixels = editor.getLineHeightInPixels()
|
||||
initialCharWidth = editor.getDefaultCharWidth()
|
||||
|
||||
component.setLineHeight(2)
|
||||
expect(editor.getLineHeightInPixels()).toBe initialLineHeightInPixels
|
||||
expect(editor.getDefaultCharWidth()).toBe initialCharWidth
|
||||
component.setFontSize(22)
|
||||
expect(editor.getLineHeightInPixels()).toBe initialLineHeightInPixels
|
||||
expect(editor.getDefaultCharWidth()).toBe initialCharWidth
|
||||
component.setFontFamily('monospace')
|
||||
expect(editor.getLineHeightInPixels()).toBe initialLineHeightInPixels
|
||||
expect(editor.getDefaultCharWidth()).toBe initialCharWidth
|
||||
|
||||
wrapperView.show()
|
||||
expect(editor.getLineHeightInPixels()).not.toBe initialLineHeightInPixels
|
||||
expect(editor.getDefaultCharWidth()).not.toBe initialCharWidth
|
||||
|
||||
it "does not re-measure character widths until the editor is shown again", ->
|
||||
wrapperView.hide()
|
||||
|
||||
component.setFontSize(22)
|
||||
|
||||
wrapperView.show()
|
||||
editor.setCursorBufferPosition([0, Infinity])
|
||||
|
||||
cursorLeft = node.querySelector('.cursor').getBoundingClientRect().left
|
||||
line0Right = node.querySelector('.line').getBoundingClientRect().right
|
||||
expect(cursorLeft).toBe line0Right
|
||||
|
||||
describe "when the fontFamily changes while the editor is hidden", ->
|
||||
it "does not attempt to measure the defaultCharWidth until the editor becomes visible again", ->
|
||||
wrapperView.hide()
|
||||
initialLineHeightInPixels = editor.getLineHeightInPixels()
|
||||
initialCharWidth = editor.getDefaultCharWidth()
|
||||
|
||||
component.setFontFamily('sans-serif')
|
||||
expect(editor.getDefaultCharWidth()).toBe initialCharWidth
|
||||
|
||||
wrapperView.show()
|
||||
expect(editor.getDefaultCharWidth()).not.toBe initialCharWidth
|
||||
|
||||
it "does not re-measure character widths until the editor is shown again", ->
|
||||
wrapperView.hide()
|
||||
|
||||
component.setFontFamily('sans-serif')
|
||||
|
||||
wrapperView.show()
|
||||
editor.setCursorBufferPosition([0, Infinity])
|
||||
|
||||
cursorLeft = node.querySelector('.cursor').getBoundingClientRect().left
|
||||
line0Right = node.querySelector('.line').getBoundingClientRect().right
|
||||
expect(cursorLeft).toBe line0Right
|
||||
|
||||
describe "when lines are changed while the editor is hidden", ->
|
||||
it "does not measure new characters until the editor is shown again", ->
|
||||
editor.setText('')
|
||||
@@ -1268,6 +1656,21 @@ describe "EditorComponent", ->
|
||||
wrapperView.show()
|
||||
expect(node.querySelector('.cursor').style['-webkit-transform']).toBe "translate3d(#{9 * charWidth}px, 0px, 0px)"
|
||||
|
||||
describe "when the editor component is resized", ->
|
||||
it "updates the component based on a new size", ->
|
||||
editor.setSoftWrap(true)
|
||||
newHeight = 4 * editor.getLineHeightInPixels() + "px"
|
||||
expect(newHeight).toBeLessThan node.style.height
|
||||
node.style.height = newHeight
|
||||
|
||||
advanceClock(component.scrollViewMeasurementInterval)
|
||||
expect(node.querySelectorAll('.line')).toHaveLength(4 + lineOverdrawMargin + 1)
|
||||
|
||||
gutterWidth = node.querySelector('.gutter').offsetWidth
|
||||
node.style.width = gutterWidth + 14 * charWidth + 'px'
|
||||
advanceClock(component.scrollViewMeasurementInterval)
|
||||
expect(node.querySelector('.line').textContent).toBe "var quicksort "
|
||||
|
||||
buildMouseEvent = (type, properties...) ->
|
||||
properties = extend({bubbles: true, cancelable: true}, properties...)
|
||||
event = new MouseEvent(type, properties)
|
||||
@@ -1276,3 +1679,17 @@ describe "EditorComponent", ->
|
||||
Object.defineProperty(event, 'target', get: -> properties.target)
|
||||
Object.defineProperty(event, 'srcObject', get: -> properties.target)
|
||||
event
|
||||
|
||||
clientCoordinatesForScreenPosition = (screenPosition) ->
|
||||
positionOffset = editor.pixelPositionForScreenPosition(screenPosition)
|
||||
scrollViewClientRect = node.querySelector('.scroll-view').getBoundingClientRect()
|
||||
clientX = scrollViewClientRect.left + positionOffset.left - editor.getScrollLeft()
|
||||
clientY = scrollViewClientRect.top + positionOffset.top - editor.getScrollTop()
|
||||
{clientX, clientY}
|
||||
|
||||
clientCoordinatesForScreenRowInGutter = (screenRow) ->
|
||||
positionOffset = editor.pixelPositionForScreenPosition([screenRow, 1])
|
||||
gutterClientRect = node.querySelector('.gutter').getBoundingClientRect()
|
||||
clientX = gutterClientRect.left + positionOffset.left - editor.getScrollLeft()
|
||||
clientY = gutterClientRect.top + positionOffset.top - editor.getScrollTop()
|
||||
{clientX, clientY}
|
||||
|
||||
+52
-103
@@ -1831,6 +1831,32 @@ describe "Editor", ->
|
||||
expect(buffer.lineForRow(1)).toBe ' var sort = function(it) {'
|
||||
expect(buffer.lineForRow(2)).toBe 'if (items.length <= 1) return items;'
|
||||
|
||||
describe '.deleteToEndOfLine()', ->
|
||||
describe 'when no text is selected', ->
|
||||
it 'deletes all text between the cursor and the end of the line', ->
|
||||
editor.setCursorBufferPosition([1, 24])
|
||||
editor.addCursorAtBufferPosition([2, 5])
|
||||
[cursor1, cursor2] = editor.getCursors()
|
||||
|
||||
editor.deleteToEndOfLine()
|
||||
expect(buffer.lineForRow(1)).toBe ' var sort = function(it'
|
||||
expect(buffer.lineForRow(2)).toBe ' i'
|
||||
expect(cursor1.getBufferPosition()).toEqual [1, 24]
|
||||
expect(cursor2.getBufferPosition()).toEqual [2, 5]
|
||||
|
||||
describe 'when at the end of the line', ->
|
||||
it 'deletes the next newline', ->
|
||||
editor.setCursorBufferPosition([1, 30])
|
||||
editor.deleteToEndOfLine()
|
||||
expect(buffer.lineForRow(1)).toBe ' var sort = function(items) { if (items.length <= 1) return items;'
|
||||
|
||||
describe 'when text is selected', ->
|
||||
it 'deletes only the text in the selection', ->
|
||||
editor.setSelectedBufferRanges([[[1, 24], [1, 27]], [[2, 0], [2, 4]]])
|
||||
editor.deleteToEndOfLine()
|
||||
expect(buffer.lineForRow(1)).toBe ' var sort = function(it) {'
|
||||
expect(buffer.lineForRow(2)).toBe 'if (items.length <= 1) return items;'
|
||||
|
||||
describe ".deleteToBeginningOfLine()", ->
|
||||
describe "when no text is selected", ->
|
||||
it "deletes all text between the cursor and the beginning of the line", ->
|
||||
@@ -3244,113 +3270,36 @@ describe "Editor", ->
|
||||
expect(editor.getScrollTop()).toBe 0
|
||||
expect(editor.getCursorBufferPosition().row).toBe 0
|
||||
|
||||
describe "decorations", ->
|
||||
decoration = null
|
||||
beforeEach ->
|
||||
decoration = {type: 'gutter', class: 'one'}
|
||||
describe ".selectPageUp/Down()", ->
|
||||
it "selects one screen height of text up or down", ->
|
||||
editor.manageScrollPosition = true
|
||||
|
||||
it "can add decorations to buffer rows and remove them", ->
|
||||
editor.addDecorationToBufferRow(2, decoration)
|
||||
editor.addDecorationToBufferRow(2, decoration)
|
||||
editor.setLineHeightInPixels(10)
|
||||
editor.setHeight(50)
|
||||
expect(editor.getScrollHeight()).toBe 130
|
||||
expect(editor.getCursorBufferPosition().row).toBe 0
|
||||
|
||||
decorations = editor.decorationsForBufferRow(2)
|
||||
expect(decorations).toHaveLength 1
|
||||
expect(decorations).toContain decoration
|
||||
editor.selectPageDown()
|
||||
expect(editor.getScrollTop()).toBe 30
|
||||
expect(editor.getSelectedBufferRanges()).toEqual [[[0,0], [5,0]]]
|
||||
|
||||
editor.removeDecorationFromBufferRow(2, decoration)
|
||||
decorations = editor.decorationsForBufferRow(2)
|
||||
expect(decorations).toHaveLength 0
|
||||
editor.selectPageDown()
|
||||
expect(editor.getScrollTop()).toBe 80
|
||||
expect(editor.getSelectedBufferRanges()).toEqual [[[0,0], [10,0]]]
|
||||
|
||||
it "can add decorations to buffer row ranges and remove them", ->
|
||||
editor.addDecorationToBufferRowRange(2, 4, decoration)
|
||||
expect(editor.decorationsForBufferRow 2).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 3).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 4).toContain decoration
|
||||
editor.selectPageDown()
|
||||
expect(editor.getScrollTop()).toBe 80
|
||||
expect(editor.getSelectedBufferRanges()).toEqual [[[0,0], [12,2]]]
|
||||
|
||||
editor.removeDecorationFromBufferRowRange(3, 5, decoration)
|
||||
expect(editor.decorationsForBufferRow 2).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 3).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 4).not.toContain decoration
|
||||
editor.moveCursorToBottom()
|
||||
editor.selectPageUp()
|
||||
expect(editor.getScrollTop()).toBe 50
|
||||
expect(editor.getSelectedBufferRanges()).toEqual [[[7,0], [12,2]]]
|
||||
|
||||
it "can add decorations associated with markers and remove them", ->
|
||||
marker = editor.displayBuffer.markBufferRange([[2, 13], [3, 15]], class: 'my-marker', invalidate: 'inside')
|
||||
editor.selectPageUp()
|
||||
expect(editor.getScrollTop()).toBe 0
|
||||
expect(editor.getSelectedBufferRanges()).toEqual [[[2,0], [12,2]]]
|
||||
|
||||
editor.addDecorationForMarker(marker, decoration)
|
||||
expect(editor.decorationsForBufferRow 1).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 2).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 3).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 4).not.toContain decoration
|
||||
|
||||
editor.getBuffer().insert([0, 0], '\n')
|
||||
expect(editor.decorationsForBufferRow 2).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 3).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 4).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 5).not.toContain decoration
|
||||
|
||||
editor.getBuffer().insert([4, 2], 'n')
|
||||
expect(editor.decorationsForBufferRow 2).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 3).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 4).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 5).not.toContain decoration
|
||||
|
||||
editor.getBuffer().undo()
|
||||
expect(editor.decorationsForBufferRow 2).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 3).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 4).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 5).not.toContain decoration
|
||||
|
||||
editor.removeDecorationForMarker(marker, decoration)
|
||||
expect(editor.decorationsForBufferRow 2).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 3).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 4).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 5).not.toContain decoration
|
||||
|
||||
describe "decorationsForBufferRow", ->
|
||||
one = {type: 'one', class: 'one'}
|
||||
two = {type: 'two', class: 'two'}
|
||||
typeless = {class: 'typeless'}
|
||||
|
||||
beforeEach ->
|
||||
editor.addDecorationToBufferRow(2, one)
|
||||
editor.addDecorationToBufferRow(2, two)
|
||||
editor.addDecorationToBufferRow(2, typeless)
|
||||
|
||||
it "returns all decorations with no decorationType specified", ->
|
||||
decorations = editor.decorationsForBufferRow(2)
|
||||
expect(decorations).toContain one
|
||||
expect(decorations).toContain two
|
||||
expect(decorations).toContain typeless
|
||||
|
||||
it "returns typeless decorations with all decorationTypes", ->
|
||||
decorations = editor.decorationsForBufferRow(2, 'one')
|
||||
expect(decorations).toContain one
|
||||
expect(decorations).not.toContain two
|
||||
expect(decorations).toContain typeless
|
||||
|
||||
describe "decorationsForBufferRowRange", ->
|
||||
one = {type: 'one', class: 'one'}
|
||||
two = {type: 'two', class: 'two'}
|
||||
typeless = {class: 'typeless'}
|
||||
|
||||
it "returns an object of decorations based on the decorationType", ->
|
||||
editor.addDecorationToBufferRow(2, one)
|
||||
editor.addDecorationToBufferRow(3, one)
|
||||
editor.addDecorationToBufferRow(5, one)
|
||||
|
||||
editor.addDecorationToBufferRow(3, two)
|
||||
editor.addDecorationToBufferRow(4, two)
|
||||
|
||||
editor.addDecorationToBufferRow(3, typeless)
|
||||
editor.addDecorationToBufferRow(5, typeless)
|
||||
|
||||
decorations = editor.decorationsForBufferRowRange(2, 5, 'one')
|
||||
expect(decorations[2]).toContain one
|
||||
|
||||
expect(decorations[3]).toContain one
|
||||
expect(decorations[3]).not.toContain two
|
||||
expect(decorations[3]).toContain typeless
|
||||
|
||||
expect(decorations[4]).toHaveLength 0
|
||||
|
||||
expect(decorations[5]).toContain one
|
||||
expect(decorations[5]).toContain typeless
|
||||
editor.selectPageUp()
|
||||
expect(editor.getScrollTop()).toBe 0
|
||||
expect(editor.getSelectedBufferRanges()).toEqual [[[0,0], [12,2]]]
|
||||
|
||||
@@ -1611,7 +1611,7 @@ describe "EditorView", ->
|
||||
editorView.attachToDom()
|
||||
|
||||
expect(atom.config.get("editor.showInvisibles")).toBeFalsy()
|
||||
expect(editorView.renderedLines.find('.line').text()).toBe " a line with tabs and spaces "
|
||||
expect(editorView.renderedLines.find('.line').text()).toBe " a line with tabs and spaces "
|
||||
|
||||
atom.config.set("editor.showInvisibles", true)
|
||||
space = editorView.invisibles?.space
|
||||
@@ -1620,10 +1620,10 @@ describe "EditorView", ->
|
||||
expect(tab).toBeTruthy()
|
||||
eol = editorView.invisibles?.eol
|
||||
expect(eol).toBeTruthy()
|
||||
expect(editorView.renderedLines.find('.line').text()).toBe "#{space}a line with tabs#{tab} and spaces#{space}#{eol}"
|
||||
expect(editorView.renderedLines.find('.line').text()).toBe "#{space}a line with tabs#{tab}and spaces#{space}#{eol}"
|
||||
|
||||
atom.config.set("editor.showInvisibles", false)
|
||||
expect(editorView.renderedLines.find('.line').text()).toBe " a line with tabs and spaces "
|
||||
expect(editorView.renderedLines.find('.line').text()).toBe " a line with tabs and spaces "
|
||||
|
||||
it "displays newlines as their own token outside of the other tokens scope", ->
|
||||
editorView.setShowInvisibles(true)
|
||||
@@ -1636,7 +1636,7 @@ describe "EditorView", ->
|
||||
editorView.attachToDom()
|
||||
atom.config.set("editor.showInvisibles", true)
|
||||
atom.config.set("editor.invisibles", eol: ";", space: "_", tab: "tab")
|
||||
expect(editorView.find(".line:first").text()).toBe "_tab _;"
|
||||
expect(editorView.find(".line:first").text()).toBe "_tab_;"
|
||||
|
||||
it "displays trailing carriage return using a visible non-empty value", ->
|
||||
editor.setText "a line that ends with a carriage return\r\n"
|
||||
@@ -2082,7 +2082,7 @@ describe "EditorView", ->
|
||||
tab = miniEditor.invisibles?.tab
|
||||
expect(tab).toBeTruthy()
|
||||
miniEditor.getEditor().setText(" a line with tabs\tand spaces ")
|
||||
expect(miniEditor.renderedLines.find('.line').text()).toBe "#{space}a line with tabs#{tab} and spaces#{space}"
|
||||
expect(miniEditor.renderedLines.find('.line').text()).toBe "#{space}a line with tabs#{tab}and spaces#{space}"
|
||||
|
||||
it "doesn't show the indent guide", ->
|
||||
atom.config.set "editor.showIndentGuide", true
|
||||
|
||||
@@ -33,7 +33,12 @@ $(window).on 'unload', ->
|
||||
$('html,body').css('overflow', 'auto')
|
||||
|
||||
jasmine.getEnv().addEqualityTester(_.isEqual) # Use underscore's definition of equality for toEqual assertions
|
||||
jasmine.getEnv().defaultTimeoutInterval = 5000
|
||||
|
||||
if process.platform is 'win32' and process.env.JANKY_SHA1
|
||||
# Use longer timeout on Windows CI
|
||||
jasmine.getEnv().defaultTimeoutInterval = 30000
|
||||
else
|
||||
jasmine.getEnv().defaultTimeoutInterval = 5000
|
||||
|
||||
specPackageName = null
|
||||
specPackagePath = null
|
||||
|
||||
@@ -344,6 +344,73 @@ describe "TokenizedBuffer", ->
|
||||
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).text).toBe "#{tabAsSpaces} buy()#{tabAsSpaces}while supply > demand"
|
||||
|
||||
it "aligns the hard tabs to the correct tab stop column", ->
|
||||
buffer.setText """
|
||||
1\t2 \t3\t4
|
||||
12\t3 \t4\t5
|
||||
123\t4 \t5\t6
|
||||
"""
|
||||
|
||||
tokenizedBuffer.setTabLength(4)
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
expect(tokenizedBuffer.lineForScreenRow(0).text).toBe "1 2 3 4"
|
||||
expect(tokenizedBuffer.lineForScreenRow(0).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.lineForScreenRow(0).tokens[1].screenDelta).toBe 3
|
||||
|
||||
expect(tokenizedBuffer.lineForScreenRow(1).text).toBe "12 3 4 5"
|
||||
expect(tokenizedBuffer.lineForScreenRow(1).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.lineForScreenRow(1).tokens[1].screenDelta).toBe 2
|
||||
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).text).toBe "123 4 5 6"
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1].screenDelta).toBe 1
|
||||
|
||||
tokenizedBuffer.setTabLength(3)
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
expect(tokenizedBuffer.lineForScreenRow(0).text).toBe "1 2 3 4"
|
||||
expect(tokenizedBuffer.lineForScreenRow(0).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.lineForScreenRow(0).tokens[1].screenDelta).toBe 2
|
||||
|
||||
expect(tokenizedBuffer.lineForScreenRow(1).text).toBe "12 3 4 5"
|
||||
expect(tokenizedBuffer.lineForScreenRow(1).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.lineForScreenRow(1).tokens[1].screenDelta).toBe 1
|
||||
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).text).toBe "123 4 5 6"
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1].screenDelta).toBe 3
|
||||
|
||||
tokenizedBuffer.setTabLength(2)
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
expect(tokenizedBuffer.lineForScreenRow(0).text).toBe "1 2 3 4"
|
||||
expect(tokenizedBuffer.lineForScreenRow(0).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.lineForScreenRow(0).tokens[1].screenDelta).toBe 1
|
||||
|
||||
expect(tokenizedBuffer.lineForScreenRow(1).text).toBe "12 3 4 5"
|
||||
expect(tokenizedBuffer.lineForScreenRow(1).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.lineForScreenRow(1).tokens[1].screenDelta).toBe 2
|
||||
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).text).toBe "123 4 5 6"
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1].screenDelta).toBe 1
|
||||
|
||||
tokenizedBuffer.setTabLength(1)
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
expect(tokenizedBuffer.lineForScreenRow(0).text).toBe "1 2 3 4"
|
||||
expect(tokenizedBuffer.lineForScreenRow(0).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.lineForScreenRow(0).tokens[1].screenDelta).toBe 1
|
||||
|
||||
expect(tokenizedBuffer.lineForScreenRow(1).text).toBe "12 3 4 5"
|
||||
expect(tokenizedBuffer.lineForScreenRow(1).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.lineForScreenRow(1).tokens[1].screenDelta).toBe 1
|
||||
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).text).toBe "123 4 5 6"
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1].bufferDelta).toBe 1
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1].screenDelta).toBe 1
|
||||
|
||||
describe "when the buffer contains surrogate pairs", ->
|
||||
beforeEach ->
|
||||
waitsForPromise ->
|
||||
|
||||
@@ -196,14 +196,14 @@ describe "WorkspaceView", ->
|
||||
atom.workspaceView.height(200)
|
||||
atom.workspaceView.attachToDom()
|
||||
rightEditorView = atom.workspaceView.getActiveView()
|
||||
rightEditorView.getEditor().setText(" \t ")
|
||||
rightEditorView.getEditor().setText("\t ")
|
||||
leftEditorView = rightEditorView.splitLeft()
|
||||
expect(rightEditorView.find(".line:first").text()).toBe " "
|
||||
expect(leftEditorView.find(".line:first").text()).toBe " "
|
||||
|
||||
{invisibles} = rightEditorView.component.state
|
||||
{space, tab, eol} = invisibles
|
||||
withInvisiblesShowing = "#{space}#{tab} #{space}#{eol}"
|
||||
withInvisiblesShowing = "#{tab} #{space}#{space}#{eol}"
|
||||
|
||||
atom.workspaceView.trigger "window:toggle-invisibles"
|
||||
expect(rightEditorView.find(".line:first").text()).toBe withInvisiblesShowing
|
||||
|
||||
@@ -34,7 +34,10 @@ CursorsComponent = React.createClass
|
||||
|
||||
shouldComponentUpdate: (newProps, newState) ->
|
||||
not newState.blinkOff is @state.blinkOff or
|
||||
not isEqualForProperties(newProps, @props, 'cursorScreenRanges', 'scrollTop', 'scrollLeft', 'lineHeightInPixels', 'defaultCharWidth')
|
||||
not isEqualForProperties(newProps, @props,
|
||||
'cursorScreenRanges', 'scrollTop', 'scrollLeft', 'lineHeightInPixels',
|
||||
'defaultCharWidth', 'scopedCharacterWidthsChangeCount'
|
||||
)
|
||||
|
||||
componentWillUpdate: (newProps) ->
|
||||
@pauseCursorBlinking() if @props.cursorScreenRanges and not isEqual(newProps.cursorScreenRanges, @props.cursorScreenRanges)
|
||||
|
||||
+81
-82
@@ -34,6 +34,7 @@ class DisplayBuffer extends Model
|
||||
horizontalScrollMargin: 6
|
||||
horizontalScrollbarHeight: 15
|
||||
verticalScrollbarWidth: 15
|
||||
scopedCharacterWidthsChangeCount: 0
|
||||
|
||||
constructor: ({tabLength, @editorWidthInChars, @tokenizedBuffer, buffer}={}) ->
|
||||
super
|
||||
@@ -43,7 +44,7 @@ class DisplayBuffer extends Model
|
||||
@charWidthsByScope = {}
|
||||
@markers = {}
|
||||
@foldsByMarkerId = {}
|
||||
@decorations = {}
|
||||
@decorationsByMarkerId = {}
|
||||
@decorationMarkerSubscriptions = {}
|
||||
@updateAllScreenLines()
|
||||
@createFoldForMarker(marker) for marker in @buffer.findMarkers(@getFoldMarkerAttributes())
|
||||
@@ -222,9 +223,11 @@ class DisplayBuffer extends Model
|
||||
|
||||
setScopedCharWidth: (scopeNames, char, width) ->
|
||||
@getScopedCharWidths(scopeNames)[char] = width
|
||||
@emit 'character-widths-changed', @scopedCharacterWidthsChangeCount++
|
||||
|
||||
setScopedCharWidths: (scopeNames, charWidths) ->
|
||||
_.extend(@getScopedCharWidths(scopeNames), charWidths)
|
||||
@emit 'character-widths-changed', @scopedCharacterWidthsChangeCount++
|
||||
|
||||
clearScopedCharWidths: ->
|
||||
@charWidthsByScope = {}
|
||||
@@ -254,15 +257,23 @@ class DisplayBuffer extends Model
|
||||
{start, end} = selection.getScreenRange()
|
||||
@intersectsVisibleRowRange(start.row, end.row + 1)
|
||||
|
||||
scrollToScreenRange: (screenRange) ->
|
||||
scrollToScreenRange: (screenRange, options) ->
|
||||
verticalScrollMarginInPixels = @getVerticalScrollMargin() * @getLineHeightInPixels()
|
||||
horizontalScrollMarginInPixels = @getHorizontalScrollMargin() * @getDefaultCharWidth()
|
||||
|
||||
{top, left, height, width} = @pixelRectForScreenRange(screenRange)
|
||||
bottom = top + height
|
||||
right = left + width
|
||||
desiredScrollTop = top - verticalScrollMarginInPixels
|
||||
desiredScrollBottom = bottom + verticalScrollMarginInPixels
|
||||
|
||||
if options?.center
|
||||
desiredScrollCenter = top + height / 2
|
||||
unless @getScrollTop() < desiredScrollCenter < @getScrollBottom()
|
||||
desiredScrollTop = desiredScrollCenter - @getHeight() / 2
|
||||
desiredScrollBottom = desiredScrollCenter + @getHeight() / 2
|
||||
else
|
||||
desiredScrollTop = top - verticalScrollMarginInPixels
|
||||
desiredScrollBottom = bottom + verticalScrollMarginInPixels
|
||||
|
||||
desiredScrollLeft = left - horizontalScrollMarginInPixels
|
||||
desiredScrollRight = right + horizontalScrollMarginInPixels
|
||||
|
||||
@@ -276,11 +287,11 @@ class DisplayBuffer extends Model
|
||||
else if desiredScrollRight > @getScrollRight()
|
||||
@setScrollRight(desiredScrollRight)
|
||||
|
||||
scrollToScreenPosition: (screenPosition) ->
|
||||
@scrollToScreenRange(new Range(screenPosition, screenPosition))
|
||||
scrollToScreenPosition: (screenPosition, options) ->
|
||||
@scrollToScreenRange(new Range(screenPosition, screenPosition), options)
|
||||
|
||||
scrollToBufferPosition: (bufferPosition) ->
|
||||
@scrollToScreenPosition(@screenPositionForBufferPosition(bufferPosition))
|
||||
scrollToBufferPosition: (bufferPosition, options) ->
|
||||
@scrollToScreenPosition(@screenPositionForBufferPosition(bufferPosition), options)
|
||||
|
||||
pixelRectForScreenRange: (screenRange) ->
|
||||
if screenRange.end.row > screenRange.start.row
|
||||
@@ -718,51 +729,19 @@ class DisplayBuffer extends Model
|
||||
rangeForAllLines: ->
|
||||
new Range([0, 0], @clipScreenPosition([Infinity, Infinity]))
|
||||
|
||||
decorationsForBufferRow: (bufferRow, decorationType) ->
|
||||
decorations = @decorations[bufferRow] ? []
|
||||
decorations = (dec for dec in decorations when not dec.type? or dec.type is decorationType) if decorationType?
|
||||
decorations
|
||||
decorationsForScreenRowRange: (startScreenRow, endScreenRow) ->
|
||||
decorationsByMarkerId = {}
|
||||
|
||||
decorationsForBufferRowRange: (startBufferRow, endBufferRow, decorationType) ->
|
||||
decorations = {}
|
||||
for bufferRow in [startBufferRow..endBufferRow]
|
||||
decorations[bufferRow] = @decorationsForBufferRow(bufferRow, decorationType)
|
||||
decorations
|
||||
for marker in @findMarkers(intersectsScreenRowRange: [startScreenRow, endScreenRow])
|
||||
if decorations = @decorationsByMarkerId[marker.id]
|
||||
decorationsByMarkerId[marker.id] = decorations
|
||||
decorationsByMarkerId
|
||||
|
||||
addDecorationToBufferRow: (bufferRow, decoration) ->
|
||||
@decorations[bufferRow] ?= []
|
||||
for current in @decorations[bufferRow]
|
||||
return if _.isEqual(current, decoration)
|
||||
@decorations[bufferRow].push(decoration)
|
||||
@emit 'decoration-changed', {bufferRow, decoration, action: 'add'}
|
||||
|
||||
removeDecorationFromBufferRow: (bufferRow, decorationPattern) ->
|
||||
return unless decorations = @decorations[bufferRow]
|
||||
|
||||
removed = []
|
||||
i = decorations.length - 1
|
||||
while i >= 0
|
||||
if @decorationMatchesPattern(decorations[i], decorationPattern)
|
||||
removed.push decorations[i]
|
||||
decorations.splice(i, 1)
|
||||
i--
|
||||
|
||||
delete @decorations[bufferRow] unless @decorations[bufferRow]?
|
||||
|
||||
for decoration in removed
|
||||
@emit 'decoration-changed', {bufferRow, decoration, action: 'remove'}
|
||||
|
||||
removed
|
||||
|
||||
addDecorationToBufferRowRange: (startBufferRow, endBufferRow, decoration) ->
|
||||
for bufferRow in [startBufferRow..endBufferRow]
|
||||
@addDecorationToBufferRow(bufferRow, decoration)
|
||||
return
|
||||
|
||||
removeDecorationFromBufferRowRange: (startBufferRow, endBufferRow, decoration) ->
|
||||
for bufferRow in [startBufferRow..endBufferRow]
|
||||
@removeDecorationFromBufferRow(bufferRow, decoration)
|
||||
return
|
||||
decorationMatchesType: (decoration, type) ->
|
||||
if _.isArray(decoration.type)
|
||||
type in decoration.type
|
||||
else
|
||||
type is decoration.type
|
||||
|
||||
decorationMatchesPattern: (decoration, decorationPattern) ->
|
||||
return false unless decoration? and decorationPattern?
|
||||
@@ -771,43 +750,43 @@ class DisplayBuffer extends Model
|
||||
true
|
||||
|
||||
addDecorationForMarker: (marker, decoration) ->
|
||||
startRow = marker.getStartBufferPosition().row
|
||||
endRow = marker.getEndBufferPosition().row
|
||||
@addDecorationToBufferRowRange(startRow, endRow, decoration)
|
||||
unless marker?
|
||||
console.warn 'A null marker cannot be decorated'
|
||||
return
|
||||
|
||||
changedSubscription = @subscribe marker, 'changed', (e) =>
|
||||
oldStartRow = e.oldHeadBufferPosition.row
|
||||
oldEndRow = e.oldTailBufferPosition.row
|
||||
newStartRow = e.newHeadBufferPosition.row
|
||||
newEndRow = e.newTailBufferPosition.row
|
||||
marker = @getMarker(marker.id)
|
||||
@decorationMarkerSubscriptions[marker.id] ?= @subscribe marker, 'destroyed', => @removeAllDecorationsForMarker(marker)
|
||||
|
||||
# swap so head is always <= than tail
|
||||
[oldEndRow, oldStartRow] = [oldStartRow, oldEndRow] if oldStartRow > oldEndRow
|
||||
[newEndRow, newStartRow] = [newStartRow, newEndRow] if newStartRow > newEndRow
|
||||
|
||||
@removeDecorationFromBufferRowRange(oldStartRow, oldEndRow, decoration)
|
||||
@addDecorationToBufferRowRange(newStartRow, newEndRow, decoration) if e.isValid
|
||||
|
||||
destroyedSubscription = @subscribe marker, 'destroyed', (e) =>
|
||||
@removeDecorationForMarker(marker, decoration)
|
||||
|
||||
@decorationMarkerSubscriptions[marker.id] ?= []
|
||||
@decorationMarkerSubscriptions[marker.id].push {decoration, changedSubscription, destroyedSubscription}
|
||||
@decorationsByMarkerId[marker.id] ?= []
|
||||
@decorationsByMarkerId[marker.id].push(decoration)
|
||||
@emit 'decoration-added', marker, decoration
|
||||
|
||||
removeDecorationForMarker: (marker, decorationPattern) ->
|
||||
unless marker?
|
||||
console.warn 'A decoration cannot be removed from a null marker'
|
||||
return
|
||||
|
||||
return unless @decorationMarkerSubscriptions[marker.id]?
|
||||
|
||||
startRow = marker.getStartBufferPosition().row
|
||||
endRow = marker.getEndBufferPosition().row
|
||||
@removeDecorationFromBufferRowRange(startRow, endRow, decorationPattern)
|
||||
decorations = @decorationsByMarkerId[marker.id]
|
||||
for i in [decorations.length - 1..0]
|
||||
decoration = decorations[i]
|
||||
if @decorationMatchesPattern(decoration, decorationPattern)
|
||||
decorations.splice(i, 1)
|
||||
@emit 'decoration-removed', marker, decoration
|
||||
|
||||
for subscription in _.clone(@decorationMarkerSubscriptions[marker.id])
|
||||
if @decorationMatchesPattern(subscription.decoration, decorationPattern)
|
||||
subscription.changedSubscription.off()
|
||||
subscription.destroyedSubscription.off()
|
||||
@decorationMarkerSubscriptions[marker.id] = _.without(@decorationMarkerSubscriptions[marker.id], subscription)
|
||||
@removedAllMarkerDecorations(marker) if decorations.length is 0
|
||||
|
||||
return
|
||||
removeAllDecorationsForMarker: (marker) ->
|
||||
decorations = @decorationsByMarkerId[marker.id].slice()
|
||||
for decoration in decorations
|
||||
@emit 'decoration-removed', marker, decoration
|
||||
@removedAllMarkerDecorations(marker)
|
||||
|
||||
removedAllMarkerDecorations: (marker) ->
|
||||
@decorationMarkerSubscriptions[marker.id].off()
|
||||
delete @decorationsByMarkerId[marker.id]
|
||||
delete @decorationMarkerSubscriptions[marker.id]
|
||||
|
||||
# Retrieves a {DisplayBufferMarker} based on its id.
|
||||
#
|
||||
@@ -912,13 +891,34 @@ class DisplayBuffer extends Model
|
||||
key = 'startRow'
|
||||
when 'endBufferRow'
|
||||
key = 'endRow'
|
||||
when 'startScreenRow'
|
||||
key = 'startRow'
|
||||
value = @bufferRowForScreenRow(value)
|
||||
when 'endScreenRow'
|
||||
key = 'endRow'
|
||||
value = @bufferRowForScreenRow(value)
|
||||
when 'intersectsBufferRowRange'
|
||||
key = 'intersectsRowRange'
|
||||
when 'intersectsScreenRowRange'
|
||||
key = 'intersectsRowRange'
|
||||
[startRow, endRow] = value
|
||||
value = [@bufferRowForScreenRow(startRow), @bufferRowForScreenRow(endRow)]
|
||||
when 'containsBufferRange'
|
||||
key = 'containsRange'
|
||||
when 'containsBufferPosition'
|
||||
key = 'containsPosition'
|
||||
when 'containedInBufferRange'
|
||||
key = 'containedInRange'
|
||||
when 'containedInScreenRange'
|
||||
key = 'containedInRange'
|
||||
value = @bufferRangeForScreenRange(value)
|
||||
when 'intersectsBufferRange'
|
||||
key = 'intersectsRange'
|
||||
when 'intersectsScreenRange'
|
||||
key = 'intersectsRange'
|
||||
value = @bufferRangeForScreenRange(value)
|
||||
bufferMarkerParams[key] = value
|
||||
|
||||
bufferMarkerParams
|
||||
|
||||
findFoldMarker: (attributes) ->
|
||||
@@ -1052,8 +1052,7 @@ class DisplayBuffer extends Model
|
||||
@emit 'marker-created', @getMarker(marker.id)
|
||||
|
||||
createFoldForMarker: (marker) ->
|
||||
bufferMarker = new DisplayBufferMarker({bufferMarker: marker, displayBuffer: this})
|
||||
@addDecorationForMarker(bufferMarker, type: 'gutter', class: 'folded')
|
||||
@addDecorationForMarker(marker, type: 'gutter', class: 'folded')
|
||||
new Fold(this, marker)
|
||||
|
||||
foldForMarker: (marker) ->
|
||||
|
||||
+167
-80
@@ -36,7 +36,10 @@ EditorComponent = React.createClass
|
||||
scrollSensitivity: 0.4
|
||||
scrollViewMeasurementRequested: false
|
||||
measureLineHeightAndDefaultCharWidthWhenShown: false
|
||||
remeasureCharacterWidthsWhenShown: false
|
||||
inputEnabled: true
|
||||
scrollViewMeasurementInterval: 100
|
||||
scopedCharacterWidthsChangeCount: null
|
||||
|
||||
render: ->
|
||||
{focused, fontSize, lineHeight, fontFamily, showIndentGuide, showInvisibles, visible} = @state
|
||||
@@ -49,8 +52,11 @@ EditorComponent = React.createClass
|
||||
renderedRowRange = @getRenderedRowRange()
|
||||
[renderedStartRow, renderedEndRow] = renderedRowRange
|
||||
cursorScreenRanges = @getCursorScreenRanges(renderedRowRange)
|
||||
selectionScreenRanges = @getSelectionScreenRanges(renderedRowRange)
|
||||
decorations = @getGutterDecorations(renderedRowRange)
|
||||
|
||||
decorations = editor.decorationsForScreenRowRange(renderedStartRow, renderedEndRow)
|
||||
highlightDecorations = @getHighlightDecorations(decorations)
|
||||
lineDecorations = @getLineDecorations(decorations)
|
||||
|
||||
scrollHeight = editor.getScrollHeight()
|
||||
scrollWidth = editor.getScrollWidth()
|
||||
scrollTop = editor.getScrollTop()
|
||||
@@ -73,9 +79,9 @@ EditorComponent = React.createClass
|
||||
|
||||
div className: className, style: {fontSize, lineHeight, fontFamily}, tabIndex: -1,
|
||||
GutterComponent {
|
||||
ref: 'gutter', editor, renderedRowRange, maxLineNumberDigits, scrollTop,
|
||||
scrollHeight, lineHeightInPixels, @pendingChanges, mouseWheelScreenRow,
|
||||
decorations
|
||||
ref: 'gutter', onMouseDown: @onGutterMouseDown, onWidthChanged: @onGutterWidthChanged,
|
||||
lineDecorations, defaultCharWidth, editor, renderedRowRange, maxLineNumberDigits, scrollViewHeight,
|
||||
scrollTop, scrollHeight, lineHeightInPixels, @pendingChanges, mouseWheelScreenRow
|
||||
}
|
||||
|
||||
div ref: 'scrollView', className: 'scroll-view', onMouseDown: @onMouseDown,
|
||||
@@ -88,13 +94,14 @@ EditorComponent = React.createClass
|
||||
|
||||
CursorsComponent {
|
||||
editor, scrollTop, scrollLeft, cursorScreenRanges, cursorBlinkPeriod, cursorBlinkResumeDelay,
|
||||
lineHeightInPixels, defaultCharWidth
|
||||
lineHeightInPixels, defaultCharWidth, @scopedCharacterWidthsChangeCount
|
||||
}
|
||||
LinesComponent {
|
||||
ref: 'lines', editor, lineHeightInPixels, defaultCharWidth,
|
||||
showIndentGuide, renderedRowRange, @pendingChanges, scrollTop, scrollLeft, @scrollingVertically,
|
||||
selectionScreenRanges, scrollHeight, scrollWidth, mouseWheelScreenRow, invisibles,
|
||||
visible, scrollViewHeight
|
||||
ref: 'lines',
|
||||
editor, lineHeightInPixels, defaultCharWidth, lineDecorations, highlightDecorations,
|
||||
showIndentGuide, renderedRowRange, @pendingChanges, scrollTop, scrollLeft,
|
||||
@scrollingVertically, scrollHeight, scrollWidth, mouseWheelScreenRow, invisibles,
|
||||
visible, scrollViewHeight, @scopedCharacterWidthsChangeCount
|
||||
}
|
||||
|
||||
ScrollbarComponent
|
||||
@@ -129,6 +136,10 @@ EditorComponent = React.createClass
|
||||
height: horizontalScrollbarHeight
|
||||
width: verticalScrollbarWidth
|
||||
|
||||
getPageRows: ->
|
||||
{editor} = @props
|
||||
Math.max(1, Math.ceil(editor.getHeight() / editor.getLineHeightInPixels()))
|
||||
|
||||
getInitialState: ->
|
||||
visible: true
|
||||
|
||||
@@ -146,6 +157,8 @@ EditorComponent = React.createClass
|
||||
componentDidMount: ->
|
||||
{editor} = @props
|
||||
|
||||
@scrollViewMeasurementIntervalId = setInterval(@requestScrollViewMeasurement, @scrollViewMeasurementInterval)
|
||||
|
||||
@observeEditor()
|
||||
@listenForDOMEvents()
|
||||
@listenForCommands()
|
||||
@@ -162,7 +175,8 @@ EditorComponent = React.createClass
|
||||
|
||||
componentWillUnmount: ->
|
||||
@unsubscribe()
|
||||
window.removeEventListener('resize', @onWindowResize)
|
||||
clearInterval(@scrollViewMeasurementIntervalId)
|
||||
@scrollViewMeasurementIntervalId = null
|
||||
|
||||
componentWillUpdate: ->
|
||||
if @props.editor.isAlive()
|
||||
@@ -175,6 +189,7 @@ EditorComponent = React.createClass
|
||||
@updateParentViewFocusedClassIfNeeded(prevState)
|
||||
@measureScrollbars() if @measuringScrollbars
|
||||
@measureLineHeightAndCharWidthsIfNeeded(prevState)
|
||||
@remeasureCharacterWidthsIfNeeded(prevState)
|
||||
@props.parentView.trigger 'editor:display-updated'
|
||||
|
||||
requestUpdate: ->
|
||||
@@ -215,33 +230,36 @@ EditorComponent = React.createClass
|
||||
cursorScreenRanges[cursor.id] = screenRange
|
||||
cursorScreenRanges
|
||||
|
||||
getSelectionScreenRanges: (renderedRowRange) ->
|
||||
getLineDecorations: (decorationsByMarkerId) ->
|
||||
{editor} = @props
|
||||
[renderedStartRow, renderedEndRow] = renderedRowRange
|
||||
decorationsByScreenRow = {}
|
||||
for markerId, decorations of decorationsByMarkerId
|
||||
marker = editor.getMarker(markerId)
|
||||
screenRange = null
|
||||
if marker.isValid()
|
||||
for decoration in decorations
|
||||
if editor.decorationMatchesType(decoration, 'gutter') or editor.decorationMatchesType(decoration, 'line')
|
||||
screenRange ?= marker.getScreenRange()
|
||||
startRow = screenRange.start.row
|
||||
endRow = screenRange.end.row
|
||||
endRow-- if not screenRange.isEmpty() and screenRange.end.column == 0
|
||||
for screenRow in [startRow..endRow]
|
||||
decorationsByScreenRow[screenRow] ?= []
|
||||
decorationsByScreenRow[screenRow].push decoration
|
||||
|
||||
selectionScreenRanges = {}
|
||||
for selection, index in editor.getSelections()
|
||||
screenRange = selection.getScreenRange()
|
||||
decorationsByScreenRow
|
||||
|
||||
if not screenRange.isEmpty() and screenRange.intersectsRowRange(renderedStartRow, renderedEndRow)
|
||||
selectionScreenRanges[selection.id] = screenRange
|
||||
|
||||
else if index is 0 # Rendering artifacts occur on the lines GPU layer if we remove the last selection
|
||||
selectionScreenRanges[selection.id] = new Range(new Point(renderedStartRow, 0), new Point(renderedStartRow, 0))
|
||||
|
||||
selectionScreenRanges
|
||||
|
||||
getGutterDecorations: (renderedRowRange) ->
|
||||
getHighlightDecorations: (decorationsByMarkerId) ->
|
||||
{editor} = @props
|
||||
[renderedStartRow, renderedEndRow] = renderedRowRange
|
||||
|
||||
bufferRows = editor.bufferRowsForScreenRows(renderedStartRow, renderedEndRow - 1)
|
||||
|
||||
decorations = {}
|
||||
for bufferRow in bufferRows
|
||||
decorations[bufferRow] = editor.decorationsForBufferRow(bufferRow, 'gutter')
|
||||
decorations[bufferRow].push {class: 'foldable'} if editor.isFoldableAtBufferRow(bufferRow)
|
||||
decorations
|
||||
filteredDecorations = {}
|
||||
for markerId, decorations of decorationsByMarkerId
|
||||
marker = editor.getMarker(markerId)
|
||||
if marker.isValid() and not marker.getScreenRange().isEmpty()
|
||||
for decoration in decorations
|
||||
if editor.decorationMatchesType(decoration, 'highlight')
|
||||
filteredDecorations[markerId] ?= {id: markerId, screenRange: marker.getScreenRange(), decorations: []}
|
||||
filteredDecorations[markerId].decorations.push decoration
|
||||
filteredDecorations
|
||||
|
||||
observeEditor: ->
|
||||
{editor} = @props
|
||||
@@ -251,7 +269,9 @@ EditorComponent = React.createClass
|
||||
@subscribe editor, 'cursors-moved', @onCursorsMoved
|
||||
@subscribe editor, 'selection-removed selection-screen-range-changed', @onSelectionChanged
|
||||
@subscribe editor, 'selection-added', @onSelectionAdded
|
||||
@subscribe editor, 'decoration-changed', @onDecorationChanged
|
||||
@subscribe editor, 'decoration-added', @onDecorationChanged
|
||||
@subscribe editor, 'decoration-removed', @onDecorationChanged
|
||||
@subscribe editor, 'character-widths-changed', @onCharacterWidthsChanged
|
||||
@subscribe editor.$scrollTop.changes, @onScrollTopChanged
|
||||
@subscribe editor.$scrollLeft.changes, @requestUpdate
|
||||
@subscribe editor.$height.changes, @requestUpdate
|
||||
@@ -266,9 +286,35 @@ EditorComponent = React.createClass
|
||||
node.addEventListener 'textInput', @onTextInput
|
||||
|
||||
scrollViewNode = @refs.scrollView.getDOMNode()
|
||||
scrollViewNode.addEventListener 'overflowchanged', @onScrollViewOverflowChanged
|
||||
scrollViewNode.addEventListener 'scroll', @onScrollViewScroll
|
||||
window.addEventListener('resize', @onWindowResize)
|
||||
window.addEventListener 'resize', @requestScrollViewMeasurement
|
||||
|
||||
@listenForIMEEvents()
|
||||
|
||||
listenForIMEEvents: ->
|
||||
node = @getDOMNode()
|
||||
{editor} = @props
|
||||
|
||||
# The IME composition events work like this:
|
||||
#
|
||||
# User types 's', chromium pops up the completion helper
|
||||
# 1. compositionstart fired
|
||||
# 2. compositionupdate fired; event.data == 's'
|
||||
# User hits arrow keys to move around in completion helper
|
||||
# 3. compositionupdate fired; event.data == 's' for each arry key press
|
||||
# User escape to cancel
|
||||
# 4. compositionend fired
|
||||
# OR User chooses a completion
|
||||
# 4. compositionend fired
|
||||
# 5. textInput fired; event.data == the completion string
|
||||
|
||||
selectedText = null
|
||||
node.addEventListener 'compositionstart', =>
|
||||
selectedText = editor.getSelectedText()
|
||||
node.addEventListener 'compositionupdate', (event) =>
|
||||
editor.insertText(event.data, select: true, undo: 'skip')
|
||||
node.addEventListener 'compositionend', =>
|
||||
editor.insertText(selectedText, select: true, undo: 'skip')
|
||||
|
||||
listenForCommands: ->
|
||||
{parentView, editor, mini} = @props
|
||||
@@ -291,6 +337,7 @@ EditorComponent = React.createClass
|
||||
'editor:consolidate-selections': @consolidateSelections
|
||||
'editor:delete-to-beginning-of-word': => editor.deleteToBeginningOfWord()
|
||||
'editor:delete-to-beginning-of-line': => editor.deleteToBeginningOfLine()
|
||||
'editor:delete-to-end-of-line': => editor.deleteToEndOfLine()
|
||||
'editor:delete-to-end-of-word': => editor.deleteToEndOfWord()
|
||||
'editor:delete-line': => editor.deleteLine()
|
||||
'editor:cut-to-end-of-line': => editor.cutToEndOfLine()
|
||||
@@ -327,10 +374,14 @@ EditorComponent = React.createClass
|
||||
'core:move-down': => editor.moveCursorDown()
|
||||
'core:move-to-top': => editor.moveCursorToTop()
|
||||
'core:move-to-bottom': => editor.moveCursorToBottom()
|
||||
'core:page-up': => editor.pageUp()
|
||||
'core:page-down': => editor.pageDown()
|
||||
'core:select-up': => editor.selectUp()
|
||||
'core:select-down': => editor.selectDown()
|
||||
'core:select-to-top': => editor.selectToTop()
|
||||
'core:select-to-bottom': => editor.selectToBottom()
|
||||
'core:select-page-up': => editor.selectPageUp()
|
||||
'core:select-page-down': => editor.selectPageDown()
|
||||
'editor:indent': => editor.indent()
|
||||
'editor:auto-indent': => editor.autoIndentSelectedRows()
|
||||
'editor:indent-selected-rows': => editor.indentSelectedRows()
|
||||
@@ -368,8 +419,6 @@ EditorComponent = React.createClass
|
||||
'editor:toggle-indent-guide': => atom.config.toggle('editor.showIndentGuide')
|
||||
'editor:toggle-line-numbers': => atom.config.toggle('editor.showLineNumbers')
|
||||
'editor:scroll-to-cursor': => editor.scrollToCursorPosition()
|
||||
'core:page-up': => editor.pageUp()
|
||||
'core:page-down': => editor.pageDown()
|
||||
'benchmark:scroll': @runScrollBenchmark
|
||||
|
||||
addCommandListeners: (listenersByCommandName) ->
|
||||
@@ -464,17 +513,12 @@ EditorComponent = React.createClass
|
||||
@pendingVerticalScrollDelta = 0
|
||||
@pendingHorizontalScrollDelta = 0
|
||||
|
||||
onScrollViewOverflowChanged: ->
|
||||
@requestScrollViewMeasurement()
|
||||
|
||||
onWindowResize: ->
|
||||
@requestScrollViewMeasurement()
|
||||
|
||||
onScrollViewScroll: ->
|
||||
console.warn "EditorScrollView scroll position changed, and it shouldn't have. If you can reproduce this, please report it."
|
||||
scrollViewNode = @refs.scrollView.getDOMNode()
|
||||
scrollViewNode.scrollTop = 0
|
||||
scrollViewNode.scrollLeft = 0
|
||||
if @isMounted()
|
||||
console.warn "EditorScrollView scrolled when it shouldn't have."
|
||||
scrollViewNode = @refs.scrollView.getDOMNode()
|
||||
scrollViewNode.scrollTop = 0
|
||||
scrollViewNode.scrollLeft = 0
|
||||
|
||||
onMouseDown: (event) ->
|
||||
return unless event.button is 0 # only handle the left mouse button
|
||||
@@ -483,6 +527,11 @@ EditorComponent = React.createClass
|
||||
{detail, shiftKey, metaKey} = event
|
||||
screenPosition = @screenPositionForMouseEvent(event)
|
||||
|
||||
if event.target?.classList.contains('fold-marker')
|
||||
bufferRow = editor.bufferRowForScreenRow(screenPosition.row)
|
||||
editor.unfoldBufferRow(bufferRow)
|
||||
return
|
||||
|
||||
if shiftKey
|
||||
editor.selectToScreenPosition(screenPosition)
|
||||
else if metaKey
|
||||
@@ -493,7 +542,46 @@ EditorComponent = React.createClass
|
||||
when 2 then editor.selectWord()
|
||||
when 3 then editor.selectLine()
|
||||
|
||||
@selectToMousePositionUntilMouseUp(event)
|
||||
@handleDragUntilMouseUp event, (screenPosition) ->
|
||||
editor.selectToScreenPosition(screenPosition)
|
||||
|
||||
onGutterMouseDown: (event) ->
|
||||
return unless event.button is 0 # only handle the left mouse button
|
||||
|
||||
if event.shiftKey
|
||||
@onGutterShiftClick(event)
|
||||
else
|
||||
@onGutterClick(event)
|
||||
|
||||
onGutterClick: (event) ->
|
||||
{editor} = @props
|
||||
clickedRow = @screenPositionForMouseEvent(event).row
|
||||
|
||||
editor.setCursorScreenPosition([clickedRow, 0])
|
||||
|
||||
@handleDragUntilMouseUp event, (screenPosition) ->
|
||||
dragRow = screenPosition.row
|
||||
if dragRow < clickedRow # dragging up
|
||||
editor.setSelectedScreenRange([[dragRow, 0], [clickedRow + 1, 0]])
|
||||
else
|
||||
editor.setSelectedScreenRange([[clickedRow, 0], [dragRow + 1, 0]])
|
||||
|
||||
onGutterShiftClick: (event) ->
|
||||
{editor} = @props
|
||||
clickedRow = @screenPositionForMouseEvent(event).row
|
||||
tailPosition = editor.getSelection().getTailScreenPosition()
|
||||
|
||||
if clickedRow < tailPosition.row
|
||||
editor.selectToScreenPosition([clickedRow, 0])
|
||||
else
|
||||
editor.selectToScreenPosition([clickedRow + 1, 0])
|
||||
|
||||
@handleDragUntilMouseUp event, (screenPosition) ->
|
||||
dragRow = screenPosition.row
|
||||
if dragRow < tailPosition.row # dragging up
|
||||
editor.setSelectedScreenRange([[dragRow, 0], tailPosition])
|
||||
else
|
||||
editor.setSelectedScreenRange([tailPosition, [dragRow + 1, 0]])
|
||||
|
||||
onStylesheetsChanged: (stylesheet) ->
|
||||
@refreshScrollbars() if @containsScrollbarSelector(stylesheet)
|
||||
@@ -550,15 +638,18 @@ EditorComponent = React.createClass
|
||||
@requestUpdate() if @isMounted()
|
||||
@decorationChangedImmediate = null
|
||||
|
||||
selectToMousePositionUntilMouseUp: (event) ->
|
||||
onCharacterWidthsChanged: (@scopedCharacterWidthsChangeCount) ->
|
||||
@requestUpdate()
|
||||
|
||||
handleDragUntilMouseUp: (event, dragHandler) ->
|
||||
{editor} = @props
|
||||
dragging = false
|
||||
lastMousePosition = {}
|
||||
|
||||
animationLoop = =>
|
||||
requestAnimationFrame =>
|
||||
if dragging
|
||||
@selectToMousePosition(lastMousePosition)
|
||||
screenPosition = @screenPositionForMouseEvent(lastMousePosition)
|
||||
dragHandler(screenPosition)
|
||||
animationLoop()
|
||||
|
||||
onMouseMove = (event) ->
|
||||
@@ -582,9 +673,6 @@ EditorComponent = React.createClass
|
||||
window.addEventListener('mousemove', onMouseMove)
|
||||
window.addEventListener('mouseup', onMouseUp)
|
||||
|
||||
selectToMousePosition: (event) ->
|
||||
@props.editor.selectToScreenPosition(@screenPositionForMouseEvent(event))
|
||||
|
||||
requestScrollViewMeasurement: ->
|
||||
return if @measurementPending
|
||||
|
||||
@@ -606,30 +694,21 @@ EditorComponent = React.createClass
|
||||
{position} = getComputedStyle(editorNode)
|
||||
{width, height} = editorNode.style
|
||||
|
||||
if position is 'absolute' or height
|
||||
clientHeight = scrollViewNode.clientHeight
|
||||
editor.setHeight(clientHeight) if clientHeight > 0
|
||||
editor.batchUpdates ->
|
||||
if position is 'absolute' or height
|
||||
clientHeight = scrollViewNode.clientHeight
|
||||
editor.setHeight(clientHeight) if clientHeight > 0
|
||||
|
||||
if position is 'absolute' or width
|
||||
clientWidth = scrollViewNode.clientWidth
|
||||
editor.setWidth(clientWidth) if clientWidth > 0
|
||||
if position is 'absolute' or width
|
||||
clientWidth = scrollViewNode.clientWidth
|
||||
editor.setWidth(clientWidth) if clientWidth > 0
|
||||
|
||||
measureLineHeightAndCharWidthsIfNeeded: (prevState) ->
|
||||
if not isEqualForProperties(prevState, @state, 'lineHeight', 'fontSize', 'fontFamily')
|
||||
{editor} = @props
|
||||
|
||||
editor.batchUpdates =>
|
||||
oldDefaultCharWidth = editor.getDefaultCharWidth()
|
||||
|
||||
if @state.visible
|
||||
@measureLineHeightAndDefaultCharWidth()
|
||||
else
|
||||
@measureLineHeightAndDefaultCharWidthWhenShown = true
|
||||
|
||||
unless oldDefaultCharWidth is editor.getDefaultCharWidth()
|
||||
@remeasureCharacterWidths()
|
||||
@measureGutter()
|
||||
|
||||
if @state.visible
|
||||
@measureLineHeightAndDefaultCharWidth()
|
||||
else
|
||||
@measureLineHeightAndDefaultCharWidthWhenShown = true
|
||||
else if @measureLineHeightAndDefaultCharWidthWhenShown and @state.visible and not prevState.visible
|
||||
@measureLineHeightAndDefaultCharWidth()
|
||||
|
||||
@@ -637,13 +716,21 @@ EditorComponent = React.createClass
|
||||
@measureLineHeightAndDefaultCharWidthWhenShown = false
|
||||
@refs.lines.measureLineHeightAndDefaultCharWidth()
|
||||
|
||||
remeasureCharacterWidthsIfNeeded: (prevState) ->
|
||||
if not isEqualForProperties(prevState, @state, 'fontSize', 'fontFamily')
|
||||
if @state.visible
|
||||
@remeasureCharacterWidths()
|
||||
else
|
||||
@remeasureCharacterWidthsWhenShown = true
|
||||
else if @remeasureCharacterWidthsWhenShown and @state.visible and not prevState.visible
|
||||
@remeasureCharacterWidths()
|
||||
|
||||
remeasureCharacterWidths: ->
|
||||
@remeasureCharacterWidthsWhenShown = false
|
||||
@refs.lines.remeasureCharacterWidths()
|
||||
|
||||
measureGutter: ->
|
||||
oldGutterWidth = @gutterWidth
|
||||
@gutterWidth = @refs.gutter.getDOMNode().offsetWidth
|
||||
@requestUpdate() if @gutterWidth isnt oldGutterWidth
|
||||
onGutterWidthChanged: (@gutterWidth) ->
|
||||
@requestUpdate()
|
||||
|
||||
measureScrollbars: ->
|
||||
@measuringScrollbars = false
|
||||
|
||||
@@ -157,6 +157,7 @@ class EditorView extends View
|
||||
'editor:consolidate-selections': (event) => @consolidateSelections(event)
|
||||
'editor:delete-to-beginning-of-word': => @editor.deleteToBeginningOfWord()
|
||||
'editor:delete-to-beginning-of-line': => @editor.deleteToBeginningOfLine()
|
||||
'editor:delete-to-end-of-line': => @editor.deleteToEndOfLine()
|
||||
'editor:delete-to-end-of-word': => @editor.deleteToEndOfWord()
|
||||
'editor:delete-line': => @editor.deleteLine()
|
||||
'editor:cut-to-end-of-line': => @editor.cutToEndOfLine()
|
||||
@@ -193,12 +194,14 @@ class EditorView extends View
|
||||
'core:move-down': => @editor.moveCursorDown()
|
||||
'core:move-to-top': => @editor.moveCursorToTop()
|
||||
'core:move-to-bottom': => @editor.moveCursorToBottom()
|
||||
'core:page-down': => @pageDown()
|
||||
'core:page-up': => @pageUp()
|
||||
'core:page-down': => @pageDown()
|
||||
'core:select-up': => @editor.selectUp()
|
||||
'core:select-down': => @editor.selectDown()
|
||||
'core:select-to-top': => @editor.selectToTop()
|
||||
'core:select-to-bottom': => @editor.selectToBottom()
|
||||
'core:select-page-up': => @editor.selectUp(@getPageRows())
|
||||
'core:select-page-down': => @editor.selectDown(@getPageRows())
|
||||
'editor:indent': => @editor.indent()
|
||||
'editor:auto-indent': => @editor.autoIndentSelectedRows()
|
||||
'editor:indent-selected-rows': => @editor.indentSelectedRows()
|
||||
|
||||
+52
-71
@@ -112,6 +112,7 @@ TextMateScopeSelector = require('first-mate').ScopeSelector
|
||||
# - {::deleteToBeginningOfWord}
|
||||
# - {::deleteToBeginningOfLine}
|
||||
# - {::delete}
|
||||
# - {::deleteToEndOfLine}
|
||||
# - {::deleteToEndOfWord}
|
||||
# - {::deleteLine}
|
||||
# - {::cutSelectedText}
|
||||
@@ -214,7 +215,9 @@ class Editor extends Model
|
||||
@subscribe @displayBuffer, 'grammar-changed', => @handleGrammarChange()
|
||||
@subscribe @displayBuffer, 'tokenized', => @handleTokenization()
|
||||
@subscribe @displayBuffer, 'soft-wrap-changed', (args...) => @emit 'soft-wrap-changed', args...
|
||||
@subscribe @displayBuffer, "decoration-changed", (e) => @emit 'decoration-changed', e
|
||||
@subscribe @displayBuffer, "decoration-added", (args...) => @emit 'decoration-added', args...
|
||||
@subscribe @displayBuffer, "decoration-removed", (args...) => @emit 'decoration-removed', args...
|
||||
@subscribe @displayBuffer, "character-widths-changed", (changeCount) => @emit 'character-widths-changed', changeCount
|
||||
|
||||
getViewClass: ->
|
||||
if atom.config.get('core.useReactEditor')
|
||||
@@ -689,6 +692,13 @@ class Editor extends Model
|
||||
delete: ->
|
||||
@mutateSelectedText (selection) -> selection.delete()
|
||||
|
||||
# Public: For each selection, if the selection is not empty, deletes the
|
||||
# selection; otherwise, deletes all characters of the containing line
|
||||
# following the cursor. If the cursor is already at the end of the line,
|
||||
# deletes the following newline.
|
||||
deleteToEndOfLine: ->
|
||||
@mutateSelectedText (selection) -> selection.deleteToEndOfLine()
|
||||
|
||||
# Public: For each selection, if the selection is empty, delete all characters
|
||||
# of the containing word following the cursor. Otherwise delete the selected
|
||||
# text.
|
||||
@@ -843,6 +853,10 @@ class Editor extends Model
|
||||
isFoldableAtBufferRow: (bufferRow) ->
|
||||
@languageMode.isFoldableAtBufferRow(bufferRow)
|
||||
|
||||
isFoldableAtScreenRow: (screenRow) ->
|
||||
bufferRow = @displayBuffer.bufferRowForScreenRow(screenRow)
|
||||
@isFoldableAtBufferRow(bufferRow)
|
||||
|
||||
# TODO: Rename to foldRowRange?
|
||||
createFold: (startRow, endRow) ->
|
||||
@displayBuffer.createFold(startRow, endRow)
|
||||
@@ -1058,41 +1072,38 @@ class Editor extends Model
|
||||
selection.insertText(fn(text))
|
||||
selection.setBufferRange(range)
|
||||
|
||||
# Public: Get all the decorations for a buffer row.
|
||||
# Public: Get all the decorations within a screen row range.
|
||||
#
|
||||
# bufferRow - the {int} buffer row
|
||||
# decorationType - the {String} decoration type to filter by eg. 'gutter'
|
||||
# startScreenRow - the {int} beginning screen row
|
||||
# endScreenRow - the {int} end screen row (inclusive)
|
||||
#
|
||||
# Returns an {Array} of decorations in the form `[{type: 'gutter', class: 'someclass'}, ...]`
|
||||
# Returns an empty array when no decorations are found
|
||||
decorationsForBufferRow: (bufferRow, decorationType) ->
|
||||
@displayBuffer.decorationsForBufferRow(bufferRow, decorationType)
|
||||
# Returns an {Object} of decorations in the form `{1: [{type: 'gutter', class: 'someclass'}], 2: ...}`
|
||||
# where the keys are markerIds, and the values are an array of {Decoration} objects attached to the marker.
|
||||
# Returns an empty object when no decorations are found
|
||||
decorationsForScreenRowRange: (startScreenRow, endScreenRow) ->
|
||||
@displayBuffer.decorationsForScreenRowRange(startScreenRow, endScreenRow)
|
||||
|
||||
# Public: Get all the decorations for a range of buffer rows (inclusive)
|
||||
# Public: Adds a decoration that tracks a {Marker}. When the marker moves,
|
||||
# is invalidated, or is destroyed, the decoration will be updated to reflect the marker's state.
|
||||
#
|
||||
# startBufferRow - the {int} start of the buffer row range
|
||||
# endBufferRow - the {int} end of the buffer row range (inclusive)
|
||||
# decorationType - the {String} decoration type to filter by eg. 'gutter'
|
||||
# There are a few supported decoration types:
|
||||
# * `gutter`: `{type: 'gutter', class: 'linter-error'}` Will add a class to the gutter rows associated with the marker.
|
||||
# * `line`: `{type: 'line', class: 'linter-error'}` Will add a class to the editor lines associated with the marker.
|
||||
# * `highlight`: `{type: 'highlight', class: 'linter-error'}` Will highlight the region of the buffer associated with the marker. Your specified class will be added to the highlight.
|
||||
#
|
||||
# Returns an {Object} of decorations in the form `{23: [{type: 'gutter', class: 'someclass'}, ...], 24: [...]}`
|
||||
# Returns an {Object} with keyed with all buffer rows in the range containing empty {Array}s when no decorations are found
|
||||
decorationsForBufferRowRange: (startBufferRow, endBufferRow, decorationType) ->
|
||||
@displayBuffer.decorationsForBufferRowRange(startBufferRow, endBufferRow, decorationType)
|
||||
|
||||
# Public: Adds a decoration to a buffer row. For example, use to mark a gutter
|
||||
# line number with a class by using the form `{type: 'gutter', class: 'linter-error'}`
|
||||
#
|
||||
# bufferRow - the {int} buffer row
|
||||
# decoration - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
|
||||
# marker - the {Marker} you want this decoration to follow
|
||||
# decoration - the {Object} decoration eg. `{type: 'gutter', class: 'linter-error'}`
|
||||
#
|
||||
# Returns nothing
|
||||
addDecorationToBufferRow: (bufferRow, decoration) ->
|
||||
@displayBuffer.addDecorationToBufferRow(bufferRow, decoration)
|
||||
addDecorationForMarker: (marker, decoration) ->
|
||||
@displayBuffer.addDecorationForMarker(marker, decoration)
|
||||
|
||||
# Public: Removes a decoration from a buffer row.
|
||||
# Public: Removes all decorations associated with a {Marker} that match a
|
||||
# `decorationPattern` and stop tracking the {Marker}.
|
||||
#
|
||||
# ```coffee
|
||||
# editor.removeDecorationFromBufferRow(2, {type: 'gutter', class: 'linter-error'})
|
||||
# marker = editor.markBufferRange([[4, 13], [5, 17]])
|
||||
# editor.removeDecorationForMarker(marker, {type: 'gutter', class: 'linter-error'})
|
||||
# ```
|
||||
#
|
||||
# All decorations matching a pattern will be removed. For example, you might
|
||||
@@ -1108,49 +1119,9 @@ class Editor extends Model
|
||||
# You can remove both with:
|
||||
#
|
||||
# ```coffee
|
||||
# editor.removeDecorationFromBufferRow(2, {namespace: 'myns'})
|
||||
# editor.removeDecorationForMarker(marker, {namespace: 'myns'})
|
||||
# ```
|
||||
#
|
||||
# bufferRow - the {int} buffer row
|
||||
# decorationPattern - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
|
||||
#
|
||||
# Returns an {Array} of the removed decorations
|
||||
removeDecorationFromBufferRow: (bufferRow, decorationPattern) ->
|
||||
@displayBuffer.removeDecorationFromBufferRow(bufferRow, decorationPattern)
|
||||
|
||||
# Public: Adds a decoration to line numbers in a buffer row range
|
||||
#
|
||||
# startBufferRow - the {int} start of the buffer row range
|
||||
# endBufferRow - the {int} end of the buffer row range (inclusive)
|
||||
# decoration - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
|
||||
#
|
||||
# Returns nothing
|
||||
addDecorationToBufferRowRange: (startBufferRow, endBufferRow, decoration) ->
|
||||
@displayBuffer.addDecorationToBufferRowRange(startBufferRow, endBufferRow, decoration)
|
||||
|
||||
# Public: Removes a decoration from line numbers in a buffer row range
|
||||
#
|
||||
# startBufferRow - the {int} start of the buffer row range
|
||||
# endBufferRow - the {int} end of the buffer row range (inclusive)
|
||||
# decoration - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
|
||||
#
|
||||
# Returns nothing
|
||||
removeDecorationFromBufferRowRange: (startBufferRow, endBufferRow, decoration) ->
|
||||
@displayBuffer.removeDecorationFromBufferRowRange(startBufferRow, endBufferRow, decoration)
|
||||
|
||||
# Public: Adds a decoration that tracks a {Marker}. When the marker moves,
|
||||
# is invalidated, or is destroyed, the decoration will be updated to reflect the marker's state.
|
||||
#
|
||||
# marker - the {Marker} you want this decoration to follow
|
||||
# decoration - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
|
||||
#
|
||||
# Returns nothing
|
||||
addDecorationForMarker: (marker, decoration) ->
|
||||
@displayBuffer.addDecorationForMarker(marker, decoration)
|
||||
|
||||
# Public: Removes all decorations associated with a {Marker} that match a
|
||||
# `decorationPattern` and stop tracking the {Marker}.
|
||||
#
|
||||
# marker - the {Marker} to detach from
|
||||
# decorationPattern - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
|
||||
#
|
||||
@@ -1158,6 +1129,9 @@ class Editor extends Model
|
||||
removeDecorationForMarker: (marker, decorationPattern) ->
|
||||
@displayBuffer.removeDecorationForMarker(marker, decorationPattern)
|
||||
|
||||
decorationMatchesType: (decoration, type) ->
|
||||
@displayBuffer.decorationMatchesType(decoration, type)
|
||||
|
||||
# Public: Get the {DisplayBufferMarker} for the given marker id.
|
||||
getMarker: (id) ->
|
||||
@displayBuffer.getMarker(id)
|
||||
@@ -1263,7 +1237,7 @@ class Editor extends Model
|
||||
addCursor: (marker) ->
|
||||
cursor = new Cursor(editor: this, marker: marker)
|
||||
@cursors.push(cursor)
|
||||
@addDecorationForMarker(marker, {class: 'cursor-line'})
|
||||
@addDecorationForMarker(marker, type: ['gutter', 'line'], class: 'cursor-line')
|
||||
@emit 'cursor-added', cursor
|
||||
cursor
|
||||
|
||||
@@ -1290,6 +1264,7 @@ class Editor extends Model
|
||||
if selection.intersectsBufferRange(selectionBufferRange)
|
||||
return selection
|
||||
else
|
||||
@addDecorationForMarker(marker, type: 'highlight', class: 'selection')
|
||||
@emit 'selection-added', selection
|
||||
selection
|
||||
|
||||
@@ -1614,6 +1589,12 @@ class Editor extends Model
|
||||
@moveCursorDown(@getRowsPerPage())
|
||||
@setScrollTop(newScrollTop)
|
||||
|
||||
selectPageUp: ->
|
||||
@selectUp(@getRowsPerPage())
|
||||
|
||||
selectPageDown: ->
|
||||
@selectDown(@getRowsPerPage())
|
||||
|
||||
# Returns the number of rows per page
|
||||
getRowsPerPage: ->
|
||||
Math.max(1, Math.ceil(@getHeight() / @getLineHeightInPixels()))
|
||||
@@ -2049,11 +2030,11 @@ class Editor extends Model
|
||||
|
||||
pixelRectForScreenRange: (screenRange) -> @displayBuffer.pixelRectForScreenRange(screenRange)
|
||||
|
||||
scrollToScreenRange: (screenRange) -> @displayBuffer.scrollToScreenRange(screenRange)
|
||||
scrollToScreenRange: (screenRange, options) -> @displayBuffer.scrollToScreenRange(screenRange, options)
|
||||
|
||||
scrollToScreenPosition: (screenPosition) -> @displayBuffer.scrollToScreenPosition(screenPosition)
|
||||
scrollToScreenPosition: (screenPosition, options) -> @displayBuffer.scrollToScreenPosition(screenPosition, options)
|
||||
|
||||
scrollToBufferPosition: (bufferPosition) -> @displayBuffer.scrollToBufferPosition(bufferPosition)
|
||||
scrollToBufferPosition: (bufferPosition, options) -> @displayBuffer.scrollToBufferPosition(bufferPosition, options)
|
||||
|
||||
horizontallyScrollable: -> @displayBuffer.horizontallyScrollable()
|
||||
|
||||
|
||||
@@ -12,20 +12,23 @@ GutterComponent = React.createClass
|
||||
mixins: [SubscriberMixin]
|
||||
|
||||
dummyLineNumberNode: null
|
||||
measuredWidth: null
|
||||
|
||||
render: ->
|
||||
{scrollHeight, scrollTop} = @props
|
||||
{scrollHeight, scrollViewHeight, scrollTop, onMouseDown} = @props
|
||||
|
||||
div className: 'gutter', onClick: @onClick,
|
||||
div className: 'line-numbers editor-colors', ref: 'lineNumbers', style:
|
||||
height: scrollHeight
|
||||
div className: 'gutter', onClick: @onClick, onMouseDown: onMouseDown,
|
||||
# The line-numbers div must have the 'editor-colors' class so it has an
|
||||
# opaque background to avoid sub-pixel anti-aliasing problems on the GPU
|
||||
div className: 'gutter line-numbers editor-colors', ref: 'lineNumbers', style:
|
||||
height: Math.max(scrollHeight, scrollViewHeight)
|
||||
WebkitTransform: "translate3d(0px, #{-scrollTop}px, 0px)"
|
||||
|
||||
componentWillMount: ->
|
||||
@lineNumberNodesById = {}
|
||||
@lineNumberIdsByScreenRow = {}
|
||||
@screenRowsByLineNumberId = {}
|
||||
@previousDecorations = {}
|
||||
@renderedDecorationsByLineNumberId = {}
|
||||
|
||||
componentDidMount: ->
|
||||
@appendDummyLineNumber()
|
||||
@@ -35,22 +38,24 @@ GutterComponent = React.createClass
|
||||
# visible row range.
|
||||
shouldComponentUpdate: (newProps) ->
|
||||
return true unless isEqualForProperties(newProps, @props,
|
||||
'renderedRowRange', 'scrollTop', 'lineHeightInPixels', 'mouseWheelScreenRow'
|
||||
'renderedRowRange', 'scrollTop', 'lineHeightInPixels', 'mouseWheelScreenRow', 'lineDecorations',
|
||||
'scrollViewHeight'
|
||||
)
|
||||
|
||||
{renderedRowRange, pendingChanges, decorations} = newProps
|
||||
{renderedRowRange, pendingChanges, lineDecorations} = newProps
|
||||
for change in pendingChanges when Math.abs(change.screenDelta) > 0 or Math.abs(change.bufferDelta) > 0
|
||||
return true unless change.end <= renderedRowRange.start or renderedRowRange.end <= change.start
|
||||
|
||||
return true unless _.isEqual(@previousDecorations, decorations)
|
||||
|
||||
false
|
||||
|
||||
componentDidUpdate: (oldProps) ->
|
||||
unless oldProps.maxLineNumberDigits is @props.maxLineNumberDigits
|
||||
unless isEqualForProperties(oldProps, @props, 'maxLineNumberDigits')
|
||||
@updateDummyLineNumber()
|
||||
@removeLineNumberNodes()
|
||||
|
||||
unless isEqualForProperties(oldProps, @props, 'maxLineNumberDigits', 'defaultCharWidth')
|
||||
@measureWidth()
|
||||
|
||||
@clearScreenRowCaches() unless oldProps.lineHeightInPixels is @props.lineHeightInPixels
|
||||
@updateLineNumbers()
|
||||
|
||||
@@ -74,7 +79,7 @@ GutterComponent = React.createClass
|
||||
@removeLineNumberNodes(lineNumberIdsToPreserve)
|
||||
|
||||
appendOrUpdateVisibleLineNumberNodes: ->
|
||||
{editor, renderedRowRange, scrollTop, maxLineNumberDigits, decorations} = @props
|
||||
{editor, renderedRowRange, scrollTop, maxLineNumberDigits, lineDecorations} = @props
|
||||
[startRow, endRow] = renderedRowRange
|
||||
|
||||
newLineNumberIds = null
|
||||
@@ -95,15 +100,17 @@ GutterComponent = React.createClass
|
||||
visibleLineNumberIds.add(id)
|
||||
|
||||
if @hasLineNumberNode(id)
|
||||
@updateLineNumberNode(id, bufferRow, screenRow, wrapCount > 0, decorations[bufferRow])
|
||||
@updateLineNumberNode(id, bufferRow, screenRow, wrapCount > 0)
|
||||
else
|
||||
newLineNumberIds ?= []
|
||||
newLineNumbersHTML ?= ""
|
||||
newLineNumberIds.push(id)
|
||||
newLineNumbersHTML += @buildLineNumberHTML(bufferRow, wrapCount > 0, maxLineNumberDigits, screenRow, decorations[bufferRow])
|
||||
newLineNumbersHTML += @buildLineNumberHTML(bufferRow, wrapCount > 0, maxLineNumberDigits, screenRow)
|
||||
@screenRowsByLineNumberId[id] = screenRow
|
||||
@lineNumberIdsByScreenRow[screenRow] = id
|
||||
|
||||
@renderedDecorationsByLineNumberId[id] = lineDecorations[screenRow]
|
||||
|
||||
if newLineNumberIds?
|
||||
WrapperDiv.innerHTML = newLineNumbersHTML
|
||||
newLineNumberNodes = toArray(WrapperDiv.children)
|
||||
@@ -114,7 +121,6 @@ GutterComponent = React.createClass
|
||||
@lineNumberNodesById[lineNumberId] = lineNumberNode
|
||||
node.appendChild(lineNumberNode)
|
||||
|
||||
@previousDecorations = decorations
|
||||
visibleLineNumberIds
|
||||
|
||||
removeLineNumberNodes: (lineNumberIdsToPreserve) ->
|
||||
@@ -126,20 +132,24 @@ GutterComponent = React.createClass
|
||||
delete @lineNumberNodesById[lineNumberId]
|
||||
delete @lineNumberIdsByScreenRow[screenRow] if @lineNumberIdsByScreenRow[screenRow] is lineNumberId
|
||||
delete @screenRowsByLineNumberId[lineNumberId]
|
||||
delete @renderedDecorationsByLineNumberId[lineNumberId]
|
||||
node.removeChild(lineNumberNode)
|
||||
|
||||
buildLineNumberHTML: (bufferRow, softWrapped, maxLineNumberDigits, screenRow, decorations) ->
|
||||
buildLineNumberHTML: (bufferRow, softWrapped, maxLineNumberDigits, screenRow) ->
|
||||
{editor, lineHeightInPixels, lineDecorations} = @props
|
||||
if screenRow?
|
||||
{lineHeightInPixels} = @props
|
||||
style = "position: absolute; top: #{screenRow * lineHeightInPixels}px;"
|
||||
else
|
||||
style = "visibility: hidden;"
|
||||
innerHTML = @buildLineNumberInnerHTML(bufferRow, softWrapped, maxLineNumberDigits)
|
||||
|
||||
classes = ''
|
||||
if decorations?
|
||||
if lineDecorations? and decorations = lineDecorations[screenRow]
|
||||
for decoration in decorations
|
||||
classes += decoration.class + ' ' if not softWrapped or softWrapped and decoration.softWrap
|
||||
if editor.decorationMatchesType(decoration, 'gutter')
|
||||
classes += decoration.class + ' '
|
||||
|
||||
classes += "foldable " if bufferRow >= 0 and editor.isFoldableAtBufferRow(bufferRow)
|
||||
classes += "line-number line-number-#{bufferRow}"
|
||||
|
||||
"<div class=\"#{classes}\" style=\"#{style}\" data-buffer-row=\"#{bufferRow}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
|
||||
@@ -154,17 +164,25 @@ GutterComponent = React.createClass
|
||||
iconHTML = '<div class="icon-right"></div>'
|
||||
padding + lineNumber + iconHTML
|
||||
|
||||
updateLineNumberNode: (lineNumberId, bufferRow, screenRow, softWrapped, decorations) ->
|
||||
updateLineNumberNode: (lineNumberId, bufferRow, screenRow, softWrapped) ->
|
||||
{editor, lineDecorations} = @props
|
||||
node = @lineNumberNodesById[lineNumberId]
|
||||
previousDecorations = @previousDecorations[bufferRow]
|
||||
|
||||
if editor.isFoldableAtBufferRow(bufferRow)
|
||||
node.classList.add('foldable')
|
||||
else
|
||||
node.classList.remove('foldable')
|
||||
|
||||
decorations = lineDecorations[screenRow]
|
||||
previousDecorations = @renderedDecorationsByLineNumberId[lineNumberId]
|
||||
|
||||
if previousDecorations?
|
||||
for decoration in previousDecorations
|
||||
node.classList.remove(decoration.class) if not contains(decorations, decoration)
|
||||
node.classList.remove(decoration.class) if editor.decorationMatchesType(decoration, 'gutter') and not _.deepContains(decorations, decoration)
|
||||
|
||||
if decorations?
|
||||
for decoration in decorations
|
||||
if not contains(previousDecorations, decoration) and (not softWrapped or softWrapped and decoration.softWrap)
|
||||
if editor.decorationMatchesType(decoration, 'gutter') and not _.deepContains(previousDecorations, decoration)
|
||||
node.classList.add(decoration.class)
|
||||
|
||||
unless @screenRowsByLineNumberId[lineNumberId] is screenRow
|
||||
@@ -192,9 +210,8 @@ GutterComponent = React.createClass
|
||||
else
|
||||
editor.foldBufferRow(bufferRow)
|
||||
|
||||
# Created because underscore uses === not _.isEqual, which we need
|
||||
contains = (array, target) ->
|
||||
return false unless array?
|
||||
for object in array
|
||||
return true if _.isEqual(object, target)
|
||||
false
|
||||
measureWidth: ->
|
||||
width = @getDOMNode().offsetWidth
|
||||
unless width is @measuredWidth
|
||||
@measuredWidth = width
|
||||
@props.onWidthChanged?(width)
|
||||
|
||||
@@ -2,17 +2,19 @@ React = require 'react-atom-fork'
|
||||
{div} = require 'reactionary-atom-fork'
|
||||
|
||||
module.exports =
|
||||
SelectionComponent = React.createClass
|
||||
displayName: 'SelectionComponent'
|
||||
HighlightComponent = React.createClass
|
||||
displayName: 'HighlightComponent'
|
||||
|
||||
render: ->
|
||||
{editor, screenRange, lineHeightInPixels} = @props
|
||||
{editor, screenRange, decoration} = @props
|
||||
{start, end} = screenRange
|
||||
rowCount = end.row - start.row + 1
|
||||
startPixelPosition = editor.pixelPositionForScreenPosition(start)
|
||||
endPixelPosition = editor.pixelPositionForScreenPosition(end)
|
||||
|
||||
div className: 'selection',
|
||||
className = 'highlight'
|
||||
className += " #{decoration.class}" if decoration.class?
|
||||
div {className},
|
||||
if rowCount is 1
|
||||
@renderSingleLineRegions(startPixelPosition, endPixelPosition)
|
||||
else
|
||||
@@ -0,0 +1,24 @@
|
||||
React = require 'react-atom-fork'
|
||||
{div} = require 'reactionary-atom-fork'
|
||||
{isEqualForProperties} = require 'underscore-plus'
|
||||
HighlightComponent = require './highlight-component'
|
||||
|
||||
module.exports =
|
||||
HighlightsComponent = React.createClass
|
||||
displayName: 'HighlightsComponent'
|
||||
|
||||
render: ->
|
||||
div className: 'highlights', @renderHighlights()
|
||||
|
||||
renderHighlights: ->
|
||||
{editor, highlightDecorations, lineHeightInPixels} = @props
|
||||
|
||||
highlightComponents = []
|
||||
for markerId, {screenRange, decorations} of highlightDecorations
|
||||
for decoration in decorations
|
||||
highlightComponents.push(HighlightComponent({key: "#{markerId}-#{decoration.class}", screenRange, decoration, editor, lineHeightInPixels}))
|
||||
|
||||
highlightComponents
|
||||
|
||||
shouldComponentUpdate: (newProps) ->
|
||||
not isEqualForProperties(newProps, @props, 'highlightDecorations', 'lineHeightInPixels', 'defaultCharWidth', 'scopedCharacterWidthsChangeCount')
|
||||
@@ -1,9 +1,10 @@
|
||||
_ = require 'underscore-plus'
|
||||
React = require 'react-atom-fork'
|
||||
{div, span} = require 'reactionary-atom-fork'
|
||||
{debounce, isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus'
|
||||
{$$} = require 'space-pen'
|
||||
|
||||
SelectionsComponent = require './selections-component'
|
||||
HighlightsComponent = require './highlights-component'
|
||||
|
||||
DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0]
|
||||
AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT}
|
||||
@@ -15,26 +16,30 @@ LinesComponent = React.createClass
|
||||
|
||||
render: ->
|
||||
if @isMounted()
|
||||
{editor, selectionScreenRanges, scrollTop, scrollLeft, scrollHeight, scrollWidth, lineHeightInPixels, defaultCharWidth, scrollViewHeight} = @props
|
||||
{editor, highlightDecorations, scrollTop, scrollLeft, scrollHeight, scrollWidth} = @props
|
||||
{lineHeightInPixels, defaultCharWidth, scrollViewHeight, scopedCharacterWidthsChangeCount} = @props
|
||||
style =
|
||||
height: Math.max(scrollHeight, scrollViewHeight)
|
||||
width: scrollWidth
|
||||
WebkitTransform: "translate3d(#{-scrollLeft}px, #{-scrollTop}px, 0px)"
|
||||
|
||||
# The lines div must have the 'editor-colors' class so it has an opaque
|
||||
# background to avoid sub-pixel anti-aliasing problems on the GPU
|
||||
div {className: 'lines editor-colors', style},
|
||||
SelectionsComponent({editor, selectionScreenRanges, lineHeightInPixels, defaultCharWidth}) if @isMounted()
|
||||
HighlightsComponent({editor, highlightDecorations, lineHeightInPixels, defaultCharWidth, scopedCharacterWidthsChangeCount}) if @isMounted()
|
||||
|
||||
componentWillMount: ->
|
||||
@measuredLines = new WeakSet
|
||||
@lineNodesByLineId = {}
|
||||
@screenRowsByLineId = {}
|
||||
@lineIdsByScreenRow = {}
|
||||
@renderedDecorationsByLineId = {}
|
||||
|
||||
shouldComponentUpdate: (newProps) ->
|
||||
return true unless isEqualForProperties(newProps, @props,
|
||||
'renderedRowRange', 'selectionScreenRanges', 'lineHeightInPixels', 'defaultCharWidth',
|
||||
'renderedRowRange', 'lineDecorations', 'highlightDecorations', 'lineHeightInPixels', 'defaultCharWidth',
|
||||
'scrollTop', 'scrollLeft', 'showIndentGuide', 'scrollingVertically', 'invisibles', 'visible',
|
||||
'scrollViewHeight', 'mouseWheelScreenRow'
|
||||
'scrollViewHeight', 'mouseWheelScreenRow', 'scopedCharacterWidthsChangeCount'
|
||||
)
|
||||
|
||||
{renderedRowRange, pendingChanges} = newProps
|
||||
@@ -57,7 +62,7 @@ LinesComponent = React.createClass
|
||||
@lineIdsByScreenRow = {}
|
||||
|
||||
updateLines: ->
|
||||
{editor, renderedRowRange, showIndentGuide, selectionChanged} = @props
|
||||
{editor, renderedRowRange, showIndentGuide, selectionChanged, lineDecorations} = @props
|
||||
[startRow, endRow] = renderedRowRange
|
||||
|
||||
visibleLines = editor.linesForScreenRows(startRow, endRow - 1)
|
||||
@@ -75,9 +80,12 @@ LinesComponent = React.createClass
|
||||
delete @lineNodesByLineId[lineId]
|
||||
delete @lineIdsByScreenRow[screenRow] if @lineIdsByScreenRow[screenRow] is lineId
|
||||
delete @screenRowsByLineId[lineId]
|
||||
delete @renderedDecorationsByLineId[lineId]
|
||||
node.removeChild(lineNode)
|
||||
|
||||
appendOrUpdateVisibleLineNodes: (visibleLines, startRow) ->
|
||||
{lineDecorations} = @props
|
||||
|
||||
newLines = null
|
||||
newLinesHTML = null
|
||||
|
||||
@@ -94,6 +102,8 @@ LinesComponent = React.createClass
|
||||
@screenRowsByLineId[line.id] = screenRow
|
||||
@lineIdsByScreenRow[screenRow] = line.id
|
||||
|
||||
@renderedDecorationsByLineId[line.id] = lineDecorations[screenRow]
|
||||
|
||||
return unless newLines?
|
||||
|
||||
WrapperDiv.innerHTML = newLinesHTML
|
||||
@@ -108,17 +118,25 @@ LinesComponent = React.createClass
|
||||
@lineNodesByLineId.hasOwnProperty(lineId)
|
||||
|
||||
buildLineHTML: (line, screenRow) ->
|
||||
{editor, mini, showIndentGuide, lineHeightInPixels} = @props
|
||||
{editor, mini, showIndentGuide, lineHeightInPixels, lineDecorations} = @props
|
||||
{tokens, text, lineEnding, fold, isSoftWrapped, indentLevel} = line
|
||||
|
||||
classes = ''
|
||||
if decorations = lineDecorations[screenRow]
|
||||
for decoration in decorations
|
||||
if editor.decorationMatchesType(decoration, 'line')
|
||||
classes += decoration.class + ' '
|
||||
classes += 'line'
|
||||
|
||||
top = screenRow * lineHeightInPixels
|
||||
lineHTML = "<div class=\"line\" style=\"position: absolute; top: #{top}px;\" data-screen-row=\"#{screenRow}\">"
|
||||
lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px;\" data-screen-row=\"#{screenRow}\">"
|
||||
|
||||
if text is ""
|
||||
lineHTML += @buildEmptyLineInnerHTML(line)
|
||||
else
|
||||
lineHTML += @buildLineInnerHTML(line)
|
||||
|
||||
lineHTML += '<span class="fold-marker"></span>' if fold
|
||||
lineHTML += "</div>"
|
||||
lineHTML
|
||||
|
||||
@@ -186,9 +204,22 @@ LinesComponent = React.createClass
|
||||
"<span class=\"#{scope.replace(/\.+/g, ' ')}\">"
|
||||
|
||||
updateLineNode: (line, screenRow) ->
|
||||
{editor, lineHeightInPixels, lineDecorations} = @props
|
||||
lineNode = @lineNodesByLineId[line.id]
|
||||
|
||||
decorations = lineDecorations[screenRow]
|
||||
previousDecorations = @renderedDecorationsByLineId[line.id]
|
||||
|
||||
if previousDecorations?
|
||||
for decoration in previousDecorations
|
||||
lineNode.classList.remove(decoration.class) if editor.decorationMatchesType(decoration, 'line') and not _.deepContains(decorations, decoration)
|
||||
|
||||
if decorations?
|
||||
for decoration in decorations
|
||||
if editor.decorationMatchesType(decoration, 'line') and not _.deepContains(previousDecorations, decoration)
|
||||
lineNode.classList.add(decoration.class)
|
||||
|
||||
unless @screenRowsByLineId[line.id] is screenRow
|
||||
{lineHeightInPixels} = @props
|
||||
lineNode = @lineNodesByLineId[line.id]
|
||||
lineNode.style.top = screenRow * lineHeightInPixels + 'px'
|
||||
lineNode.dataset.screenRow = screenRow
|
||||
@screenRowsByLineId[line.id] = screenRow
|
||||
@@ -214,13 +245,15 @@ LinesComponent = React.createClass
|
||||
@measureCharactersInNewLines()
|
||||
|
||||
measureCharactersInNewLines: ->
|
||||
{editor} = @props
|
||||
[visibleStartRow, visibleEndRow] = @props.renderedRowRange
|
||||
node = @getDOMNode()
|
||||
|
||||
for tokenizedLine in @props.editor.linesForScreenRows(visibleStartRow, visibleEndRow - 1)
|
||||
unless @measuredLines.has(tokenizedLine)
|
||||
lineNode = @lineNodesByLineId[tokenizedLine.id]
|
||||
@measureCharactersInLine(tokenizedLine, lineNode)
|
||||
editor.batchUpdates =>
|
||||
for tokenizedLine in editor.linesForScreenRows(visibleStartRow, visibleEndRow - 1)
|
||||
unless @measuredLines.has(tokenizedLine)
|
||||
lineNode = @lineNodesByLineId[tokenizedLine.id]
|
||||
@measureCharactersInLine(tokenizedLine, lineNode)
|
||||
|
||||
measureCharactersInLine: (tokenizedLine, lineNode) ->
|
||||
{editor} = @props
|
||||
|
||||
@@ -5,9 +5,7 @@ EditorComponent = require './editor-component'
|
||||
|
||||
module.exports =
|
||||
class ReactEditorView extends View
|
||||
# The `overlayer` class is included for backwards compatibility with
|
||||
# context menus. It should be removed in v1.0.0
|
||||
@content: -> @div class: 'editor react overlayer'
|
||||
@content: -> @div class: 'editor react'
|
||||
|
||||
focusOnAttach: false
|
||||
|
||||
@@ -36,7 +34,7 @@ class ReactEditorView extends View
|
||||
node = @component.getDOMNode()
|
||||
|
||||
@scrollView = $(node).find('.scroll-view')
|
||||
@underlayer = $(node).find('.selections').addClass('underlayer')
|
||||
@underlayer = $(node).find('.highlights').addClass('underlayer')
|
||||
@overlayer = $(node).find('.lines').addClass('overlayer')
|
||||
@hiddenInput = $(node).find('.hidden-input')
|
||||
|
||||
@@ -71,11 +69,11 @@ class ReactEditorView extends View
|
||||
scrollToBottom: ->
|
||||
@editor.setScrollBottom(Infinity)
|
||||
|
||||
scrollToScreenPosition: (screenPosition) ->
|
||||
@editor.scrollToScreenPosition(screenPosition)
|
||||
scrollToScreenPosition: (screenPosition, options) ->
|
||||
@editor.scrollToScreenPosition(screenPosition, options)
|
||||
|
||||
scrollToBufferPosition: (bufferPosition) ->
|
||||
@editor.scrollToBufferPosition(bufferPosition)
|
||||
scrollToBufferPosition: (bufferPosition, options) ->
|
||||
@editor.scrollToBufferPosition(bufferPosition, options)
|
||||
|
||||
scrollToCursorPosition: ->
|
||||
@editor.scrollToCursorPosition()
|
||||
|
||||
+23
-1
@@ -91,6 +91,18 @@ class Selection extends Model
|
||||
end = Math.max(start, end - 1) if range.end.column == 0
|
||||
[start, end]
|
||||
|
||||
getTailScreenPosition: ->
|
||||
@marker.getTailScreenPosition()
|
||||
|
||||
getTailBufferPosition: ->
|
||||
@marker.getTailBufferPosition()
|
||||
|
||||
getHeadScreenPosition: ->
|
||||
@marker.getHeadScreenPosition()
|
||||
|
||||
getHeadBufferPosition: ->
|
||||
@marker.getHeadBufferPosition()
|
||||
|
||||
autoscroll: ->
|
||||
@editor.scrollToScreenRange(@getScreenRange())
|
||||
|
||||
@@ -266,7 +278,8 @@ class Selection extends Model
|
||||
|
||||
# FIXME: I have no idea what this does.
|
||||
getGoalBufferRange: ->
|
||||
@marker.getAttributes().goalBufferRange
|
||||
if goalBufferRange = @marker.getAttributes().goalBufferRange
|
||||
Range.fromObject(goalBufferRange)
|
||||
|
||||
# Public: Moves the selection up one row.
|
||||
addSelectionAbove: ->
|
||||
@@ -430,6 +443,15 @@ class Selection extends Model
|
||||
@selectRight()
|
||||
@deleteSelectedText()
|
||||
|
||||
# Public: If the selection is empty, removes all text from the cursor to the
|
||||
# end of the line. If the cursor is already at the end of the line, it
|
||||
# removes the following newline. If the selection isn't empty, only deletes
|
||||
# the contents of the selection.
|
||||
deleteToEndOfLine: ->
|
||||
return @delete() if @isEmpty() and @cursor.isAtEndOfLine()
|
||||
@selectToEndOfLine() if @isEmpty()
|
||||
@deleteSelectedText()
|
||||
|
||||
# Public: Removes the selection or all characters from the start of the
|
||||
# selection to the end of the current word if nothing is selected.
|
||||
deleteToEndOfWord: ->
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
React = require 'react-atom-fork'
|
||||
{div} = require 'reactionary-atom-fork'
|
||||
{isEqualForProperties} = require 'underscore-plus'
|
||||
SelectionComponent = require './selection-component'
|
||||
|
||||
module.exports =
|
||||
SelectionsComponent = React.createClass
|
||||
displayName: 'SelectionsComponent'
|
||||
|
||||
render: ->
|
||||
div className: 'selections', @renderSelections()
|
||||
|
||||
renderSelections: ->
|
||||
{editor, selectionScreenRanges, lineHeightInPixels} = @props
|
||||
|
||||
selectionComponents = []
|
||||
for selectionId, screenRange of selectionScreenRanges
|
||||
selectionComponents.push(SelectionComponent({key: selectionId, screenRange, editor, lineHeightInPixels}))
|
||||
selectionComponents
|
||||
|
||||
componentWillMount: ->
|
||||
@selectionRanges = {}
|
||||
|
||||
shouldComponentUpdate: (newProps) ->
|
||||
not isEqualForProperties(newProps, @props, 'selectionScreenRanges', 'lineHeightInPixels', 'defaultCharWidth')
|
||||
+18
-11
@@ -42,16 +42,18 @@ class Token
|
||||
whitespaceRegexForTabLength: (tabLength) ->
|
||||
WhitespaceRegexesByTabLength[tabLength] ?= new RegExp("([ ]{#{tabLength}})|(\t)|([^\t]+)", "g")
|
||||
|
||||
breakOutAtomicTokens: (tabLength, breakOutLeadingSoftTabs) ->
|
||||
breakOutAtomicTokens: (tabLength, breakOutLeadingSoftTabs, startColumn) ->
|
||||
if @hasSurrogatePair
|
||||
outputTokens = []
|
||||
column = startColumn
|
||||
|
||||
for token in @breakOutSurrogatePairs()
|
||||
if token.isAtomic
|
||||
outputTokens.push(token)
|
||||
else
|
||||
outputTokens.push(token.breakOutAtomicTokens(tabLength, breakOutLeadingSoftTabs)...)
|
||||
outputTokens.push(token.breakOutAtomicTokens(tabLength, breakOutLeadingSoftTabs, column)...)
|
||||
breakOutLeadingSoftTabs = token.isOnlyWhitespace() if breakOutLeadingSoftTabs
|
||||
column += token.value.length
|
||||
|
||||
outputTokens
|
||||
else
|
||||
@@ -64,17 +66,21 @@ class Token
|
||||
|
||||
outputTokens = []
|
||||
regex = @whitespaceRegexForTabLength(tabLength)
|
||||
column = startColumn
|
||||
while match = regex.exec(@value)
|
||||
[fullMatch, softTab, hardTab] = match
|
||||
token = null
|
||||
if softTab and breakOutLeadingSoftTabs
|
||||
outputTokens.push(@buildSoftTabToken(tabLength))
|
||||
token = @buildSoftTabToken(tabLength)
|
||||
else if hardTab
|
||||
breakOutLeadingSoftTabs = false
|
||||
outputTokens.push(@buildHardTabToken(tabLength))
|
||||
token = @buildHardTabToken(tabLength, column)
|
||||
else
|
||||
breakOutLeadingSoftTabs = false
|
||||
value = match[0]
|
||||
outputTokens.push(new Token({value, @scopes}))
|
||||
token = new Token({value, @scopes})
|
||||
column += token.value.length
|
||||
outputTokens.push(token)
|
||||
|
||||
outputTokens
|
||||
|
||||
@@ -105,17 +111,18 @@ class Token
|
||||
isAtomic: true
|
||||
)
|
||||
|
||||
buildHardTabToken: (tabLength) ->
|
||||
@buildTabToken(tabLength, true)
|
||||
buildHardTabToken: (tabLength, column) ->
|
||||
@buildTabToken(tabLength, true, column)
|
||||
|
||||
buildSoftTabToken: (tabLength) ->
|
||||
@buildTabToken(tabLength, false)
|
||||
@buildTabToken(tabLength, false, 0)
|
||||
|
||||
buildTabToken: (tabLength, isHardTab) ->
|
||||
buildTabToken: (tabLength, isHardTab, column=0) ->
|
||||
tabStop = tabLength - (column % tabLength)
|
||||
new Token(
|
||||
value: _.multiplyString(" ", tabLength)
|
||||
value: _.multiplyString(" ", tabStop)
|
||||
scopes: @scopes
|
||||
bufferDelta: if isHardTab then 1 else tabLength
|
||||
bufferDelta: if isHardTab then 1 else tabStop
|
||||
isAtomic: true
|
||||
isHardTab: isHardTab
|
||||
)
|
||||
|
||||
@@ -5,8 +5,8 @@ idCounter = 1
|
||||
module.exports =
|
||||
class TokenizedLine
|
||||
constructor: ({tokens, @lineEnding, @ruleStack, @startBufferColumn, @fold, @tabLength, @indentLevel}) ->
|
||||
@tokens = @breakOutAtomicTokens(tokens)
|
||||
@startBufferColumn ?= 0
|
||||
@tokens = @breakOutAtomicTokens(tokens)
|
||||
@text = @buildText()
|
||||
@bufferDelta = @buildBufferDelta()
|
||||
|
||||
@@ -124,8 +124,11 @@ class TokenizedLine
|
||||
breakOutAtomicTokens: (inputTokens) ->
|
||||
outputTokens = []
|
||||
breakOutLeadingSoftTabs = true
|
||||
column = @startBufferColumn
|
||||
for token in inputTokens
|
||||
outputTokens.push(token.breakOutAtomicTokens(@tabLength, breakOutLeadingSoftTabs)...)
|
||||
newTokens = token.breakOutAtomicTokens(@tabLength, breakOutLeadingSoftTabs, column)
|
||||
column += newToken.value.length for newToken in newTokens
|
||||
outputTokens.push(newTokens...)
|
||||
breakOutLeadingSoftTabs = token.isOnlyWhitespace() if breakOutLeadingSoftTabs
|
||||
outputTokens
|
||||
|
||||
|
||||
@@ -151,6 +151,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.editor .fold-marker {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.editor .fold-marker:after {
|
||||
.icon(0.8em, inline);
|
||||
content: @ellipsis;
|
||||
@@ -251,6 +255,12 @@
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.editor .highlight {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.editor .highlight .region,
|
||||
.editor .selection .region {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário