Comparar commits
289 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 2ceccf5239 | |||
| ad8bd98c9a | |||
| 6c4e138ec0 | |||
| 9ad1021c2d | |||
| aeb5a84696 | |||
| 228836447c | |||
| b760d3e4fc | |||
| 1d634e471e | |||
| babbdbf9e5 | |||
| e9d820e8a8 | |||
| 16e05347a7 | |||
| 2574ee5936 | |||
| 283966dbb9 | |||
| f8727ffa6f | |||
| 1f31918373 | |||
| 104aa5efc7 | |||
| 7fe0f5b445 | |||
| c1260d68bb | |||
| 08748b5bad | |||
| 752aa9a8e9 | |||
| e4639281f8 | |||
| f53d489abb | |||
| 628c2f82bd | |||
| df8a6437a5 | |||
| 1f768a21f0 | |||
| b13385b281 | |||
| 68d74e7de0 | |||
| c730e3c67e | |||
| 22496ceeb1 | |||
| a03f2f46ee | |||
| afec8f1ca0 | |||
| 43e6fb73f1 | |||
| a271e52a4e | |||
| 274ca33959 | |||
| 10d6ec156f | |||
| 4e27e765d0 | |||
| 168cda4f75 | |||
| fdccc0bcc2 | |||
| d566726b9f | |||
| e9f2a536ed | |||
| 2532527a6a | |||
| f10076c87d | |||
| 083f65ed5d | |||
| f59a8f1e68 | |||
| bef554709f | |||
| e5379515b9 | |||
| a0ff6f5325 | |||
| dd4b6a6d28 | |||
| 51ee591282 | |||
| 19a5269a5f | |||
| 9b6fa967be | |||
| 201e00aa83 | |||
| f02d956362 | |||
| 798739f837 | |||
| 216d561c79 | |||
| 6607f99c6c | |||
| 3a42346e5e | |||
| ae9f79bfc4 | |||
| 5a9a3c62e1 | |||
| d678f367db | |||
| febfb120c8 | |||
| addbe80e8a | |||
| b96abfffb7 | |||
| a6f2e926fe | |||
| 550a4ce906 | |||
| 1a56b487a1 | |||
| 4fa9c64c2b | |||
| 56e5fb7a63 | |||
| efa72bcb1c | |||
| 033db8997b | |||
| 64a487eebb | |||
| ddc677fb30 | |||
| fe6a007774 | |||
| 48d90e3dfc | |||
| f457b41a81 | |||
| 93388c2048 | |||
| 66f3f2d883 | |||
| 3657dc0bf4 | |||
| 274288161d | |||
| 0d03e388f1 | |||
| 507106d35b | |||
| eeba559ec7 | |||
| 14bfa90004 | |||
| 0ec6cbe141 | |||
| e952ab2e02 | |||
| 5c2eb053d8 | |||
| aee552476a | |||
| 355abef2cf | |||
| cec62c56a6 | |||
| 35ea4e6de4 | |||
| 8c266957f1 | |||
| f1f93f2f70 | |||
| de773e4f75 | |||
| 205e10fd09 | |||
| ca4dd5a29a | |||
| 2517765821 | |||
| 4b9871fa13 | |||
| 28dd7d4acd | |||
| e3eb51c135 | |||
| 9ec38ddb0d | |||
| 7a4dc0b9a4 | |||
| 6b4ce5f205 | |||
| 9a3f8022ad | |||
| a2a625a7bb | |||
| 495b1571ca | |||
| c862ccbc56 | |||
| 59709a92ba | |||
| 022f5ca219 | |||
| 96ebb9bf03 | |||
| 95b24fb933 | |||
| 9bdc78df2e | |||
| 241731f9c8 | |||
| 6997adece9 | |||
| d0a917ed14 | |||
| 7fc2e0b540 | |||
| 1c48f60e42 | |||
| 6b10fcc2f8 | |||
| cf27826156 | |||
| 5e38add177 | |||
| b4af0a79d0 | |||
| 616b9e4b7d | |||
| 985662b8f0 | |||
| cfdea7e73f | |||
| 48135a1e8d | |||
| 81233a696b | |||
| 84bb624b5b | |||
| 171631d20f | |||
| 486a7937b5 | |||
| 1162af61ed | |||
| a931aaff53 | |||
| 0fd8c5441c | |||
| f5551929d8 | |||
| 4a501a7c31 | |||
| 44413912bc | |||
| d0c61eb2be | |||
| 5c9a5cdc85 | |||
| 2b0ef68255 | |||
| f60f9b9f4f | |||
| 4f10500102 | |||
| 096afcf6f3 | |||
| 2204571ef5 | |||
| cbad8a56ec | |||
| c4be3069f7 | |||
| accee294dc | |||
| 1239ab540f | |||
| 5a0d7e716b | |||
| af9355e4e6 | |||
| 7738e74df0 | |||
| 3a433f734c | |||
| b0ad5e2f69 | |||
| 0c960552f0 | |||
| 8772e96935 | |||
| e24196c0ef | |||
| b879221a96 | |||
| 97a353b31a | |||
| 57e2cf80f4 | |||
| 669586c11b | |||
| 5b79bb7f66 | |||
| fd2ed9a1bc | |||
| 95bf08dfa0 | |||
| 3d3b72a954 | |||
| 724babdcb6 | |||
| bad2cebd6e | |||
| 565b611c18 | |||
| 1b8f23722b | |||
| ab02d5f25f | |||
| e472d7b038 | |||
| ba83b0ede0 | |||
| 96e6ddac2e | |||
| 08bd03b706 | |||
| 2f42f23ec6 | |||
| 5a8ca1ae66 | |||
| 9898cbc52e | |||
| 53cc5c9856 | |||
| 148a9f0248 | |||
| 48e2302ccb | |||
| 6327094696 | |||
| c4fdb54650 | |||
| a55c329226 | |||
| e365e51a2b | |||
| c44fd62eb1 | |||
| 9c2d321327 | |||
| 70e5880b1d | |||
| 61d9ff4ba4 | |||
| e4c1bf10f5 | |||
| c2858fcae2 | |||
| fddd411279 | |||
| 2dda577d7c | |||
| 958bc638d7 | |||
| 33ed403818 | |||
| 3c69fd2d49 | |||
| a134a60ce8 | |||
| 9c49a2d970 | |||
| 8cd9160ed5 | |||
| 8ad13d3045 | |||
| cc8ba2d679 | |||
| da36d5f40f | |||
| 9904a9a197 | |||
| 7250a9e407 | |||
| 1a02b2ec98 | |||
| 94c860e936 | |||
| 1aeb32eb81 | |||
| e450e61248 | |||
| fd7951b2c6 | |||
| 743e79b659 | |||
| 3d10c3856b | |||
| d5df83e872 | |||
| 248948b217 | |||
| 7d3553659a | |||
| 28dad3a01a | |||
| be872c2fdd | |||
| 526d87c355 | |||
| cbeb0187da | |||
| 7c614c6c79 | |||
| f4f0b4be72 | |||
| 3e27beeafa | |||
| 4a6b70d261 | |||
| 5917f71685 | |||
| fb42c499b1 | |||
| 1583271e2f | |||
| 020e22a13d | |||
| a37a7e0a76 | |||
| d1b4d0f9f2 | |||
| fb111dd0f4 | |||
| 1392deb29c | |||
| 2dafc5eaa1 | |||
| a407b0107a | |||
| 36c3c38047 | |||
| e56317bd64 | |||
| da49751f24 | |||
| 762559d2b7 | |||
| 2a0070ef10 | |||
| 7c7de4436e | |||
| 87afa50477 | |||
| 2e71e2fa4b | |||
| b537ba8864 | |||
| c156119882 | |||
| fea958c8e4 | |||
| 8e0a695f7c | |||
| 47f99cd74e | |||
| 0eb5fd4c3b | |||
| 0c01de350d | |||
| 77641f138b | |||
| f24843a928 | |||
| 03ab77c60b | |||
| ec0ec3bb16 | |||
| 690fc7180a | |||
| 02553bf8e2 | |||
| 81646532b4 | |||
| 1e7c80aebf | |||
| 27fe5b784c | |||
| 9b2901583e | |||
| 408665d7d2 | |||
| 6ec58e1163 | |||
| 541ffa5057 | |||
| 0ea683e0b5 | |||
| f12004d27b | |||
| 35d268fcf8 | |||
| 20c3ca21e3 | |||
| 37d2e00f4e | |||
| 5bf9bbe764 | |||
| e37fc316fb | |||
| 9128041c20 | |||
| 020c5e795a | |||
| be3a6bc6d0 | |||
| 142bb4b615 | |||
| ddf7bb5303 | |||
| 442342937f | |||
| a967e52f68 | |||
| c475e89a3e | |||
| 58da88b71a | |||
| 71328f21bd | |||
| a1b0f6c25d | |||
| fdccc2dae4 | |||
| 1246f8ed20 | |||
| fb56817895 | |||
| b6d8d5d100 | |||
| 8eb23dbe5c | |||
| b4c8cb4bf9 | |||
| 8988d55d25 | |||
| 5824b127ed | |||
| d6ec73886f | |||
| 411cf579f4 | |||
| ad0ed7e634 | |||
| 7c00e02b1a | |||
| e6040972d8 | |||
| ac2f723aba | |||
| 326d1dce14 | |||
| c2f87fb73b |
+35
-32
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "atom",
|
||||
"productName": "Atom",
|
||||
"version": "0.86.0",
|
||||
"version": "0.92.0",
|
||||
"description": "A hackable text editor for the 21st Century.",
|
||||
"main": "./src/browser/main.js",
|
||||
"repository": {
|
||||
@@ -15,19 +15,19 @@
|
||||
"atomShellVersion": "0.11.10",
|
||||
"dependencies": {
|
||||
"async": "0.2.6",
|
||||
"atom-keymap": "^0.17.0",
|
||||
"atom-keymap": "^0.18.0",
|
||||
"bootstrap": "git://github.com/atom/bootstrap.git#6af81906189f1747fd6c93479e3d998ebe041372",
|
||||
"clear-cut": "0.4.0",
|
||||
"coffee-script": "1.7.0",
|
||||
"coffeestack": "0.7.0",
|
||||
"delegato": "1.x",
|
||||
"emissary": "^1.2.1",
|
||||
"first-mate": "^1.5.1",
|
||||
"fs-plus": "^2.2.1",
|
||||
"first-mate": "^1.5.2",
|
||||
"fs-plus": "^2.2.2",
|
||||
"fstream": "0.1.24",
|
||||
"fuzzaldrin": "~1.1",
|
||||
"git-utils": "^1.2.2",
|
||||
"grim": "0.6.0",
|
||||
"grim": "0.9.0",
|
||||
"guid": "0.0.10",
|
||||
"jasmine-tagged": "^1.1.1",
|
||||
"less-cache": "0.12.0",
|
||||
@@ -36,10 +36,12 @@
|
||||
"nslog": "0.5.0",
|
||||
"oniguruma": "^1.0.6",
|
||||
"optimist": "0.4.0",
|
||||
"pathwatcher": "^1.1.1",
|
||||
"pathwatcher": "^1.2.1",
|
||||
"property-accessors": "1.x",
|
||||
"q": "^1.0.1",
|
||||
"random-words": "0.0.1",
|
||||
"react": "^0.10.0",
|
||||
"reactionary": "^0.8.0",
|
||||
"runas": "0.5.x",
|
||||
"scandal": "0.15.2",
|
||||
"scoped-property-store": "^0.8.0",
|
||||
@@ -49,77 +51,78 @@
|
||||
"serializable": "1.x",
|
||||
"space-pen": "3.1.1",
|
||||
"temp": "0.5.0",
|
||||
"text-buffer": "^2.0.1",
|
||||
"text-buffer": "^2.1.0",
|
||||
"theorist": "1.x",
|
||||
"underscore-plus": "^1.1.2",
|
||||
"underscore-plus": "^1.2.1",
|
||||
"vm-compatibility-layer": "0.1.0"
|
||||
},
|
||||
"packageDependencies": {
|
||||
"atom-dark-syntax": "0.15.0",
|
||||
"atom-dark-ui": "0.26.0",
|
||||
"atom-light-syntax": "0.16.0",
|
||||
"atom-light-syntax": "0.17.0",
|
||||
"atom-light-ui": "0.24.0",
|
||||
"base16-tomorrow-dark-theme": "0.13.0",
|
||||
"base16-tomorrow-dark-theme": "0.15.0",
|
||||
"solarized-dark-syntax": "0.14.0",
|
||||
"solarized-light-syntax": "0.7.0",
|
||||
"archive-view": "0.30.0",
|
||||
"autocomplete": "0.27.0",
|
||||
"autoflow": "0.16.0",
|
||||
"autosave": "0.13.0",
|
||||
"background-tips": "0.10.0",
|
||||
"background-tips": "0.13.0",
|
||||
"bookmarks": "0.22.0",
|
||||
"bracket-matcher": "0.30.0",
|
||||
"command-palette": "0.20.0",
|
||||
"bracket-matcher": "0.33.0",
|
||||
"command-palette": "0.21.0",
|
||||
"deprecation-cop": "0.5.0",
|
||||
"dev-live-reload": "0.30.0",
|
||||
"exception-reporting": "0.17.0",
|
||||
"feedback": "0.30.0",
|
||||
"find-and-replace": "0.97.0",
|
||||
"fuzzy-finder": "0.49.0",
|
||||
"find-and-replace": "0.99.0",
|
||||
"fuzzy-finder": "0.50.0",
|
||||
"git-diff": "0.28.0",
|
||||
"go-to-line": "0.19.0",
|
||||
"grammar-selector": "0.24.0",
|
||||
"image-view": "0.32.0",
|
||||
"image-view": "0.33.0",
|
||||
"keybinding-resolver": "0.17.0",
|
||||
"link": "0.22.0",
|
||||
"markdown-preview": "0.62.0",
|
||||
"markdown-preview": "0.65.0",
|
||||
"metrics": "0.32.0",
|
||||
"open-on-github": "0.28.0",
|
||||
"package-generator": "0.30.0",
|
||||
"release-notes": "0.26.0",
|
||||
"settings-view": "0.107.0",
|
||||
"snippets": "0.40.0",
|
||||
"spell-check": "0.33.0",
|
||||
"status-bar": "0.39.0",
|
||||
"styleguide": "0.28.0",
|
||||
"release-notes": "0.27.0",
|
||||
"settings-view": "0.111.0",
|
||||
"snippets": "0.41.0",
|
||||
"spell-check": "0.34.0",
|
||||
"status-bar": "0.40.0",
|
||||
"styleguide": "0.29.0",
|
||||
"symbols-view": "0.49.0",
|
||||
"tabs": "0.34.0",
|
||||
"timecop": "0.17.0",
|
||||
"tree-view": "0.89.0",
|
||||
"tabs": "0.37.0",
|
||||
"timecop": "0.18.0",
|
||||
"tree-view": "0.90.0",
|
||||
"update-package-dependencies": "0.6.0",
|
||||
"welcome": "0.12.0",
|
||||
"whitespace": "0.22.0",
|
||||
"wrap-guide": "0.18.0",
|
||||
"language-c": "0.14.0",
|
||||
"language-coffee-script": "0.22.0",
|
||||
"language-css": "0.13.0",
|
||||
"language-css": "0.16.0",
|
||||
"language-gfm": "0.31.0",
|
||||
"language-git": "0.9.0",
|
||||
"language-go": "0.8.0",
|
||||
"language-go": "0.9.0",
|
||||
"language-html": "0.19.0",
|
||||
"language-hyperlink": "0.9.0",
|
||||
"language-java": "0.9.0",
|
||||
"language-javascript": "0.22.0",
|
||||
"language-javascript": "0.24.0",
|
||||
"language-json": "0.8.0",
|
||||
"language-less": "0.6.0",
|
||||
"language-less": "0.8.0",
|
||||
"language-make": "0.10.0",
|
||||
"language-objective-c": "0.11.0",
|
||||
"language-perl": "0.8.0",
|
||||
"language-php": "0.14.0",
|
||||
"language-property-list": "0.7.0",
|
||||
"language-python": "0.15.0",
|
||||
"language-ruby": "0.22.0",
|
||||
"language-ruby": "0.23.0",
|
||||
"language-ruby-on-rails": "0.12.0",
|
||||
"language-sass": "0.8.0",
|
||||
"language-sass": "0.10.0",
|
||||
"language-shellscript": "0.8.0",
|
||||
"language-source": "0.7.0",
|
||||
"language-sql": "0.8.0",
|
||||
|
||||
+1
-1
@@ -23,9 +23,9 @@ var commands = [
|
||||
[home, '.atom', '.node-gyp'],
|
||||
[home, '.atom', 'storage'],
|
||||
[home, '.atom', '.npm'],
|
||||
[home, '.atom', 'compile-cache'],
|
||||
[tmpdir, 'atom-build'],
|
||||
[tmpdir, 'atom-cached-atom-shells'],
|
||||
[tmpdir, 'atom-compile-cache'],
|
||||
];
|
||||
var run = function() {
|
||||
var next = commands.shift();
|
||||
|
||||
@@ -36,15 +36,53 @@ describe "Config", ->
|
||||
|
||||
describe "when the value equals the default value", ->
|
||||
it "does not store the value", ->
|
||||
atom.config.setDefaults("foo", same: 1, changes: 1)
|
||||
atom.config.setDefaults "foo",
|
||||
same: 1
|
||||
changes: 1
|
||||
sameArray: [1, 2, 3]
|
||||
sameObject: {a: 1, b: 2}
|
||||
null: null
|
||||
undefined: undefined
|
||||
expect(atom.config.settings.foo).toBeUndefined()
|
||||
|
||||
atom.config.set('foo.same', 1)
|
||||
atom.config.set('foo.changes', 2)
|
||||
atom.config.set('foo.sameArray', [1, 2, 3])
|
||||
atom.config.set('foo.null', undefined)
|
||||
atom.config.set('foo.undefined', null)
|
||||
atom.config.set('foo.sameObject', {b: 2, a: 1})
|
||||
expect(atom.config.settings.foo).toEqual {changes: 2}
|
||||
|
||||
atom.config.set('foo.changes', 1)
|
||||
expect(atom.config.settings.foo).toEqual {}
|
||||
|
||||
describe ".getDefault(keyPath)", ->
|
||||
it "returns a clone of the default value", ->
|
||||
atom.config.setDefaults("foo", same: 1, changes: 1)
|
||||
expect(atom.config.getDefault('foo.same')).toBe 1
|
||||
expect(atom.config.getDefault('foo.changes')).toBe 1
|
||||
|
||||
atom.config.set('foo.same', 2)
|
||||
atom.config.set('foo.changes', 3)
|
||||
expect(atom.config.getDefault('foo.same')).toBe 1
|
||||
expect(atom.config.getDefault('foo.changes')).toBe 1
|
||||
|
||||
initialDefaultValue = [1, 2, 3]
|
||||
atom.config.setDefaults("foo", bar: initialDefaultValue)
|
||||
expect(atom.config.getDefault('foo.bar')).toEqual initialDefaultValue
|
||||
expect(atom.config.getDefault('foo.bar')).not.toBe initialDefaultValue
|
||||
|
||||
describe ".isDefault(keyPath)", ->
|
||||
it "returns true when the value of the key path is its default value", ->
|
||||
atom.config.setDefaults("foo", same: 1, changes: 1)
|
||||
expect(atom.config.isDefault('foo.same')).toBe true
|
||||
expect(atom.config.isDefault('foo.changes')).toBe true
|
||||
|
||||
atom.config.set('foo.same', 2)
|
||||
atom.config.set('foo.changes', 3)
|
||||
expect(atom.config.isDefault('foo.same')).toBe false
|
||||
expect(atom.config.isDefault('foo.changes')).toBe false
|
||||
|
||||
describe ".toggle(keyPath)", ->
|
||||
it "negates the boolean value of the current key path value", ->
|
||||
atom.config.set('foo.a', 1)
|
||||
|
||||
@@ -82,7 +82,7 @@ describe "DisplayBuffer", ->
|
||||
|
||||
describe "when there is no whitespace before the boundary", ->
|
||||
it "wraps the line exactly at the boundary since there's no more graceful place to wrap it", ->
|
||||
buffer.change([[0, 0], [1, 0]], 'abcdefghijklmnopqrstuvwxyz\n')
|
||||
buffer.setTextInRange([[0, 0], [1, 0]], 'abcdefghijklmnopqrstuvwxyz\n')
|
||||
displayBuffer.setEditorWidthInChars(10)
|
||||
expect(displayBuffer.lineForRow(0).text).toBe 'abcdefghij'
|
||||
expect(displayBuffer.lineForRow(1).text).toBe 'klmnopqrst'
|
||||
@@ -142,7 +142,7 @@ describe "DisplayBuffer", ->
|
||||
|
||||
describe "when buffer lines are removed", ->
|
||||
it "removes lines and emits a change event", ->
|
||||
buffer.change([[3, 21], [7, 5]], ';')
|
||||
buffer.setTextInRange([[3, 21], [7, 5]], ';')
|
||||
expect(displayBuffer.lineForRow(3).text).toBe ' var pivot = items;'
|
||||
expect(displayBuffer.lineForRow(4).text).toBe ' return '
|
||||
expect(displayBuffer.lineForRow(5).text).toBe 'sort(left).concat(pivot).concat(sort(right));'
|
||||
@@ -330,7 +330,7 @@ describe "DisplayBuffer", ->
|
||||
|
||||
describe "when the old range surrounds a fold", ->
|
||||
beforeEach ->
|
||||
buffer.change([[1, 0], [5, 1]], 'party!')
|
||||
buffer.setTextInRange([[1, 0], [5, 1]], 'party!')
|
||||
|
||||
it "removes the fold and replaces the selection with the new text", ->
|
||||
expect(displayBuffer.lineForRow(0).text).toBe "0"
|
||||
@@ -352,7 +352,7 @@ describe "DisplayBuffer", ->
|
||||
displayBuffer.createFold(2, 9)
|
||||
changeHandler.reset()
|
||||
|
||||
buffer.change([[1, 0], [10, 0]], 'goodbye')
|
||||
buffer.setTextInRange([[1, 0], [10, 0]], 'goodbye')
|
||||
|
||||
expect(displayBuffer.lineForRow(0).text).toBe "0"
|
||||
expect(displayBuffer.lineForRow(1).text).toBe "goodbye10"
|
||||
@@ -371,7 +371,7 @@ describe "DisplayBuffer", ->
|
||||
describe "when the old range precedes lines with a fold", ->
|
||||
describe "when the new range precedes lines with a fold", ->
|
||||
it "updates the buffer and re-positions subsequent folds", ->
|
||||
buffer.change([[0, 0], [1, 1]], 'abc')
|
||||
buffer.setTextInRange([[0, 0], [1, 1]], 'abc')
|
||||
|
||||
expect(displayBuffer.lineForRow(0).text).toBe "abc"
|
||||
expect(displayBuffer.lineForRow(1).fold).toBe fold1
|
||||
@@ -394,7 +394,7 @@ describe "DisplayBuffer", ->
|
||||
|
||||
describe "when the old range straddles the beginning of a fold", ->
|
||||
it "destroys the fold", ->
|
||||
buffer.change([[1, 1], [3, 0]], "a\nb\nc\nd\n")
|
||||
buffer.setTextInRange([[1, 1], [3, 0]], "a\nb\nc\nd\n")
|
||||
expect(displayBuffer.lineForRow(1).text).toBe '1a'
|
||||
expect(displayBuffer.lineForRow(2).text).toBe 'b'
|
||||
expect(displayBuffer.lineForRow(2).fold).toBeUndefined()
|
||||
@@ -402,7 +402,7 @@ describe "DisplayBuffer", ->
|
||||
|
||||
describe "when the old range follows a fold", ->
|
||||
it "re-positions the screen ranges for the change event based on the preceding fold", ->
|
||||
buffer.change([[10, 0], [11, 0]], 'abc')
|
||||
buffer.setTextInRange([[10, 0], [11, 0]], 'abc')
|
||||
|
||||
expect(displayBuffer.lineForRow(1).text).toBe "1"
|
||||
expect(displayBuffer.lineForRow(2).fold).toBe fold1
|
||||
@@ -430,7 +430,7 @@ describe "DisplayBuffer", ->
|
||||
|
||||
describe "when the end of the new range exceeds the end of the fold", ->
|
||||
it "expands the fold to contain all the inserted lines", ->
|
||||
buffer.change([[3, 0], [4, 0]], 'a\nb\nc\nd\n')
|
||||
buffer.setTextInRange([[3, 0], [4, 0]], 'a\nb\nc\nd\n')
|
||||
expect(fold1.getStartRow()).toBe 2
|
||||
expect(fold1.getEndRow()).toBe 7
|
||||
|
||||
@@ -447,7 +447,7 @@ describe "DisplayBuffer", ->
|
||||
describe "when the end of the new range precedes the end of the fold", ->
|
||||
it "destroys the fold", ->
|
||||
fold2.destroy()
|
||||
buffer.change([[3, 0], [6, 0]], 'a\n')
|
||||
buffer.setTextInRange([[3, 0], [6, 0]], 'a\n')
|
||||
expect(displayBuffer.lineForRow(2).text).toBe '2'
|
||||
expect(displayBuffer.lineForRow(2).fold).toBeUndefined()
|
||||
expect(displayBuffer.lineForRow(3).text).toBe 'a'
|
||||
@@ -831,7 +831,7 @@ describe "DisplayBuffer", ->
|
||||
expect(marker.getTailScreenPosition()).toEqual [5, 7]
|
||||
expect(marker2.isValid()).toBeFalsy()
|
||||
|
||||
buffer.change([[8, 0], [8, 2]], ".....")
|
||||
buffer.setTextInRange([[8, 0], [8, 2]], ".....")
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
expect(markerChangedHandler).toHaveBeenCalled()
|
||||
expect(marker2ChangedHandler).toHaveBeenCalled()
|
||||
@@ -943,3 +943,69 @@ describe "DisplayBuffer", ->
|
||||
expect(displayBuffer.getMarkerCount()).toBe initialMarkerCount + 2
|
||||
expect(marker1.getAttributes()).toEqual a: 1, b: 2
|
||||
expect(marker2.getAttributes()).toEqual a: 1, b: 3
|
||||
|
||||
describe "DisplayBufferMarker::getPixelRange()", ->
|
||||
it "returns the start and end positions of the marker based on the line height and character widths assigned to the DisplayBuffer", ->
|
||||
marker = displayBuffer.markScreenRange([[5, 10], [6, 4]])
|
||||
|
||||
displayBuffer.setLineHeight(20)
|
||||
displayBuffer.setDefaultCharWidth(10)
|
||||
displayBuffer.setScopedCharWidths(["source.js", "keyword.control.js"], r: 11, e: 11, t: 11, u: 11, n: 11)
|
||||
|
||||
{start, end} = marker.getPixelRange()
|
||||
expect(start.top).toBe 5 * 20
|
||||
expect(start.left).toBe (4 * 10) + (6 * 11)
|
||||
|
||||
describe "::setScrollTop", ->
|
||||
beforeEach ->
|
||||
displayBuffer.manageScrollPosition = true
|
||||
displayBuffer.setLineHeight(10)
|
||||
|
||||
it "disallows negative values", ->
|
||||
displayBuffer.setHeight(displayBuffer.getScrollHeight() + 100)
|
||||
expect(displayBuffer.setScrollTop(-10)).toBe 0
|
||||
expect(displayBuffer.getScrollTop()).toBe 0
|
||||
|
||||
it "disallows values that would make ::getScrollBottom() exceed ::getScrollHeight()", ->
|
||||
displayBuffer.setHeight(50)
|
||||
maxScrollTop = displayBuffer.getScrollHeight() - displayBuffer.getHeight()
|
||||
|
||||
expect(displayBuffer.setScrollTop(maxScrollTop)).toBe maxScrollTop
|
||||
expect(displayBuffer.getScrollTop()).toBe maxScrollTop
|
||||
|
||||
expect(displayBuffer.setScrollTop(maxScrollTop + 50)).toBe maxScrollTop
|
||||
expect(displayBuffer.getScrollTop()).toBe maxScrollTop
|
||||
|
||||
describe "::setScrollLeft", ->
|
||||
beforeEach ->
|
||||
displayBuffer.manageScrollPosition = true
|
||||
displayBuffer.setDefaultCharWidth(10)
|
||||
|
||||
it "disallows negative values", ->
|
||||
displayBuffer.setWidth(displayBuffer.getScrollWidth() + 100)
|
||||
expect(displayBuffer.setScrollLeft(-10)).toBe 0
|
||||
expect(displayBuffer.getScrollLeft()).toBe 0
|
||||
|
||||
it "disallows values that would make ::getScrollRight() exceed ::getScrollWidth()", ->
|
||||
displayBuffer.setWidth(50)
|
||||
maxScrollLeft = displayBuffer.getScrollWidth() - displayBuffer.getWidth()
|
||||
|
||||
expect(displayBuffer.setScrollLeft(maxScrollLeft)).toBe maxScrollLeft
|
||||
expect(displayBuffer.getScrollLeft()).toBe maxScrollLeft
|
||||
|
||||
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", ->
|
||||
displayBuffer.manageScrollPosition = true
|
||||
displayBuffer.setLineHeight(10)
|
||||
displayBuffer.setDefaultCharWidth(10)
|
||||
|
||||
displayBuffer.setHeight(50)
|
||||
displayBuffer.setWidth(50)
|
||||
maxScrollTop = displayBuffer.getScrollHeight() - displayBuffer.getHeight()
|
||||
|
||||
displayBuffer.scrollToScreenPosition([8, 20])
|
||||
expect(displayBuffer.getScrollBottom()).toBe (9 + displayBuffer.getVerticalScrollMargin()) * 10
|
||||
expect(displayBuffer.getScrollRight()).toBe (20 + displayBuffer.getHorizontalScrollMargin()) * 10
|
||||
|
||||
@@ -0,0 +1,580 @@
|
||||
{extend, flatten, toArray} = require 'underscore-plus'
|
||||
ReactEditorView = require '../src/react-editor-view'
|
||||
nbsp = String.fromCharCode(160)
|
||||
|
||||
describe "EditorComponent", ->
|
||||
[editor, wrapperView, component, node, verticalScrollbarNode, horizontalScrollbarNode] = []
|
||||
[lineHeightInPixels, charWidth, delayAnimationFrames, nextAnimationFrame] = []
|
||||
|
||||
beforeEach ->
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-javascript')
|
||||
|
||||
runs ->
|
||||
spyOn(window, "setInterval").andCallFake window.fakeSetInterval
|
||||
spyOn(window, "clearInterval").andCallFake window.fakeClearInterval
|
||||
|
||||
delayAnimationFrames = false
|
||||
nextAnimationFrame = null
|
||||
spyOn(window, 'requestAnimationFrame').andCallFake (fn) ->
|
||||
if delayAnimationFrames
|
||||
nextAnimationFrame = fn
|
||||
else
|
||||
fn()
|
||||
|
||||
editor = atom.project.openSync('sample.js')
|
||||
wrapperView = new ReactEditorView(editor)
|
||||
wrapperView.attachToDom()
|
||||
{component} = wrapperView
|
||||
component.setLineHeight(1.3)
|
||||
component.setFontSize(20)
|
||||
|
||||
lineHeightInPixels = editor.getLineHeight()
|
||||
charWidth = editor.getDefaultCharWidth()
|
||||
node = component.getDOMNode()
|
||||
verticalScrollbarNode = node.querySelector('.vertical-scrollbar')
|
||||
horizontalScrollbarNode = node.querySelector('.horizontal-scrollbar')
|
||||
|
||||
describe "line rendering", ->
|
||||
it "renders only the currently-visible lines", ->
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
component.measureHeightAndWidth()
|
||||
|
||||
lines = node.querySelectorAll('.line')
|
||||
expect(lines.length).toBe 6
|
||||
expect(lines[0].textContent).toBe editor.lineForScreenRow(0).text
|
||||
expect(lines[5].textContent).toBe editor.lineForScreenRow(5).text
|
||||
|
||||
verticalScrollbarNode.scrollTop = 2.5 * lineHeightInPixels
|
||||
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
|
||||
|
||||
expect(node.querySelector('.scroll-view-content').style['-webkit-transform']).toBe "translate3d(0px, #{-2.5 * lineHeightInPixels}px, 0)"
|
||||
|
||||
lineNodes = node.querySelectorAll('.line')
|
||||
expect(lineNodes.length).toBe 6
|
||||
expect(lineNodes[0].offsetTop).toBe 2 * lineHeightInPixels
|
||||
expect(lineNodes[0].textContent).toBe editor.lineForScreenRow(2).text
|
||||
expect(lineNodes[5].textContent).toBe editor.lineForScreenRow(7).text
|
||||
|
||||
it "updates absolute positions of subsequent lines when lines are inserted or removed", ->
|
||||
editor.getBuffer().deleteRows(0, 1)
|
||||
lineNodes = node.querySelectorAll('.line')
|
||||
expect(lineNodes[0].offsetTop).toBe 0
|
||||
expect(lineNodes[1].offsetTop).toBe 1 * lineHeightInPixels
|
||||
expect(lineNodes[2].offsetTop).toBe 2 * lineHeightInPixels
|
||||
|
||||
editor.getBuffer().insert([0, 0], '\n\n')
|
||||
lineNodes = node.querySelectorAll('.line')
|
||||
expect(lineNodes[0].offsetTop).toBe 0
|
||||
expect(lineNodes[1].offsetTop).toBe 1 * lineHeightInPixels
|
||||
expect(lineNodes[2].offsetTop).toBe 2 * lineHeightInPixels
|
||||
expect(lineNodes[3].offsetTop).toBe 3 * lineHeightInPixels
|
||||
expect(lineNodes[4].offsetTop).toBe 4 * lineHeightInPixels
|
||||
|
||||
describe "when indent guides are enabled", ->
|
||||
beforeEach ->
|
||||
component.setShowIndentGuide(true)
|
||||
|
||||
it "adds an 'indent-guide' class to spans comprising the leading whitespace", ->
|
||||
lines = node.querySelectorAll('.line')
|
||||
line1LeafNodes = getLeafNodes(lines[1])
|
||||
expect(line1LeafNodes[0].textContent).toBe ' '
|
||||
expect(line1LeafNodes[0].classList.contains('indent-guide')).toBe true
|
||||
expect(line1LeafNodes[1].classList.contains('indent-guide')).toBe false
|
||||
|
||||
line2LeafNodes = getLeafNodes(lines[2])
|
||||
expect(line2LeafNodes[0].textContent).toBe ' '
|
||||
expect(line2LeafNodes[0].classList.contains('indent-guide')).toBe true
|
||||
expect(line2LeafNodes[1].textContent).toBe ' '
|
||||
expect(line2LeafNodes[1].classList.contains('indent-guide')).toBe true
|
||||
expect(line2LeafNodes[2].classList.contains('indent-guide')).toBe false
|
||||
|
||||
it "renders leading whitespace spans with the 'indent-guide' class for empty lines", ->
|
||||
editor.getBuffer().insert([1, Infinity], '\n')
|
||||
|
||||
lines = node.querySelectorAll('.line')
|
||||
line2LeafNodes = getLeafNodes(lines[2])
|
||||
|
||||
expect(line2LeafNodes.length).toBe 3
|
||||
expect(line2LeafNodes[0].textContent).toBe ' '
|
||||
expect(line2LeafNodes[0].classList.contains('indent-guide')).toBe true
|
||||
expect(line2LeafNodes[1].textContent).toBe ' '
|
||||
expect(line2LeafNodes[1].classList.contains('indent-guide')).toBe true
|
||||
expect(line2LeafNodes[2].textContent).toBe ' '
|
||||
expect(line2LeafNodes[2].classList.contains('indent-guide')).toBe true
|
||||
|
||||
it "renders indent guides correctly on lines containing only whitespace", ->
|
||||
editor.getBuffer().insert([1, Infinity], '\n ')
|
||||
lines = node.querySelectorAll('.line')
|
||||
line2LeafNodes = getLeafNodes(lines[2])
|
||||
expect(line2LeafNodes.length).toBe 3
|
||||
expect(line2LeafNodes[0].textContent).toBe ' '
|
||||
expect(line2LeafNodes[0].classList.contains('indent-guide')).toBe true
|
||||
expect(line2LeafNodes[1].textContent).toBe ' '
|
||||
expect(line2LeafNodes[1].classList.contains('indent-guide')).toBe true
|
||||
expect(line2LeafNodes[2].textContent).toBe ' '
|
||||
expect(line2LeafNodes[2].classList.contains('indent-guide')).toBe true
|
||||
|
||||
it "does not render indent guides in trailing whitespace for lines containing non whitespace characters", ->
|
||||
editor.getBuffer().setText (" hi ")
|
||||
lines = node.querySelectorAll('.line')
|
||||
line0LeafNodes = getLeafNodes(lines[0])
|
||||
expect(line0LeafNodes[0].textContent).toBe ' '
|
||||
expect(line0LeafNodes[0].classList.contains('indent-guide')).toBe true
|
||||
expect(line0LeafNodes[1].textContent).toBe ' '
|
||||
expect(line0LeafNodes[1].classList.contains('indent-guide')).toBe false
|
||||
|
||||
getLeafNodes = (node) ->
|
||||
if node.children.length > 0
|
||||
flatten(toArray(node.children).map(getLeafNodes))
|
||||
else
|
||||
[node]
|
||||
|
||||
describe "gutter rendering", ->
|
||||
it "renders the currently-visible line numbers", ->
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
component.measureHeightAndWidth()
|
||||
|
||||
lines = node.querySelectorAll('.line-number')
|
||||
expect(lines.length).toBe 6
|
||||
expect(lines[0].textContent).toBe "#{nbsp}1"
|
||||
expect(lines[5].textContent).toBe "#{nbsp}6"
|
||||
|
||||
verticalScrollbarNode.scrollTop = 2.5 * lineHeightInPixels
|
||||
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
|
||||
|
||||
expect(node.querySelector('.line-numbers').style['-webkit-transform']).toBe "translate3d(0, #{-2.5 * lineHeightInPixels}px, 0)"
|
||||
|
||||
lineNumberNodes = node.querySelectorAll('.line-number')
|
||||
expect(lineNumberNodes.length).toBe 6
|
||||
expect(lineNumberNodes[0].offsetTop).toBe 2 * lineHeightInPixels
|
||||
expect(lineNumberNodes[5].offsetTop).toBe 7 * lineHeightInPixels
|
||||
expect(lineNumberNodes[0].textContent).toBe "#{nbsp}3"
|
||||
expect(lineNumberNodes[5].textContent).toBe "#{nbsp}8"
|
||||
|
||||
it "updates absolute positions of subsequent line numbers when lines are inserted or removed", ->
|
||||
editor.getBuffer().insert([0, 0], '\n\n')
|
||||
|
||||
lineNumberNodes = node.querySelectorAll('.line-number')
|
||||
expect(lineNumberNodes[0].offsetTop).toBe 0
|
||||
expect(lineNumberNodes[1].offsetTop).toBe 1 * lineHeightInPixels
|
||||
expect(lineNumberNodes[2].offsetTop).toBe 2 * lineHeightInPixels
|
||||
expect(lineNumberNodes[3].offsetTop).toBe 3 * lineHeightInPixels
|
||||
expect(lineNumberNodes[4].offsetTop).toBe 4 * lineHeightInPixels
|
||||
|
||||
editor.getBuffer().insert([0, 0], '\n\n')
|
||||
lineNumberNodes = node.querySelectorAll('.line-number')
|
||||
expect(lineNumberNodes[0].offsetTop).toBe 0
|
||||
expect(lineNumberNodes[1].offsetTop).toBe 1 * lineHeightInPixels
|
||||
expect(lineNumberNodes[2].offsetTop).toBe 2 * lineHeightInPixels
|
||||
expect(lineNumberNodes[3].offsetTop).toBe 3 * lineHeightInPixels
|
||||
expect(lineNumberNodes[4].offsetTop).toBe 4 * lineHeightInPixels
|
||||
|
||||
it "renders • characters for soft-wrapped lines", ->
|
||||
editor.setSoftWrap(true)
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
node.style.width = 30 * charWidth + 'px'
|
||||
component.measureHeightAndWidth()
|
||||
|
||||
lines = node.querySelectorAll('.line-number')
|
||||
expect(lines.length).toBe 6
|
||||
expect(lines[0].textContent).toBe "#{nbsp}1"
|
||||
expect(lines[1].textContent).toBe "#{nbsp}•"
|
||||
expect(lines[2].textContent).toBe "#{nbsp}2"
|
||||
expect(lines[3].textContent).toBe "#{nbsp}•"
|
||||
expect(lines[4].textContent).toBe "#{nbsp}3"
|
||||
expect(lines[5].textContent).toBe "#{nbsp}•"
|
||||
|
||||
describe "cursor rendering", ->
|
||||
it "renders the currently visible cursors", ->
|
||||
cursor1 = editor.getCursor()
|
||||
cursor1.setScreenPosition([0, 5])
|
||||
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
component.measureHeightAndWidth()
|
||||
|
||||
cursorNodes = node.querySelectorAll('.cursor')
|
||||
expect(cursorNodes.length).toBe 1
|
||||
expect(cursorNodes[0].offsetHeight).toBe lineHeightInPixels
|
||||
expect(cursorNodes[0].offsetWidth).toBe charWidth
|
||||
expect(cursorNodes[0].offsetTop).toBe 0
|
||||
expect(cursorNodes[0].offsetLeft).toBe 5 * charWidth
|
||||
|
||||
cursor2 = editor.addCursorAtScreenPosition([6, 11])
|
||||
cursor3 = editor.addCursorAtScreenPosition([4, 10])
|
||||
|
||||
cursorNodes = node.querySelectorAll('.cursor')
|
||||
expect(cursorNodes.length).toBe 2
|
||||
expect(cursorNodes[0].offsetTop).toBe 0
|
||||
expect(cursorNodes[0].offsetLeft).toBe 5 * charWidth
|
||||
expect(cursorNodes[1].offsetTop).toBe 4 * lineHeightInPixels
|
||||
expect(cursorNodes[1].offsetLeft).toBe 10 * charWidth
|
||||
|
||||
verticalScrollbarNode.scrollTop = 2.5 * lineHeightInPixels
|
||||
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
|
||||
|
||||
cursorNodes = node.querySelectorAll('.cursor')
|
||||
expect(cursorNodes.length).toBe 2
|
||||
expect(cursorNodes[0].offsetTop).toBe 6 * lineHeightInPixels
|
||||
expect(cursorNodes[0].offsetLeft).toBe 11 * charWidth
|
||||
expect(cursorNodes[1].offsetTop).toBe 4 * lineHeightInPixels
|
||||
expect(cursorNodes[1].offsetLeft).toBe 10 * charWidth
|
||||
|
||||
cursor3.destroy()
|
||||
cursorNodes = node.querySelectorAll('.cursor')
|
||||
expect(cursorNodes.length).toBe 1
|
||||
expect(cursorNodes[0].offsetTop).toBe 6 * lineHeightInPixels
|
||||
expect(cursorNodes[0].offsetLeft).toBe 11 * charWidth
|
||||
|
||||
it "accounts for character widths when positioning cursors", ->
|
||||
atom.config.set('editor.fontFamily', 'sans-serif')
|
||||
editor.setCursorScreenPosition([0, 16])
|
||||
|
||||
cursor = node.querySelector('.cursor')
|
||||
cursorRect = cursor.getBoundingClientRect()
|
||||
|
||||
cursorLocationTextNode = node.querySelector('.storage.type.function.js').firstChild.firstChild
|
||||
range = document.createRange()
|
||||
range.setStart(cursorLocationTextNode, 0)
|
||||
range.setEnd(cursorLocationTextNode, 1)
|
||||
rangeRect = range.getBoundingClientRect()
|
||||
|
||||
expect(cursorRect.left).toBe rangeRect.left
|
||||
expect(cursorRect.width).toBe rangeRect.width
|
||||
|
||||
it "blinks cursors when they aren't moving", ->
|
||||
editor.addCursorAtScreenPosition([1, 0])
|
||||
[cursorNode1, cursorNode2] = node.querySelectorAll('.cursor')
|
||||
expect(cursorNode1.classList.contains('blink-off')).toBe false
|
||||
expect(cursorNode2.classList.contains('blink-off')).toBe false
|
||||
|
||||
advanceClock(component.props.cursorBlinkPeriod / 2)
|
||||
expect(cursorNode1.classList.contains('blink-off')).toBe true
|
||||
expect(cursorNode2.classList.contains('blink-off')).toBe true
|
||||
|
||||
advanceClock(component.props.cursorBlinkPeriod / 2)
|
||||
expect(cursorNode1.classList.contains('blink-off')).toBe false
|
||||
expect(cursorNode2.classList.contains('blink-off')).toBe false
|
||||
|
||||
advanceClock(component.props.cursorBlinkPeriod / 2)
|
||||
expect(cursorNode1.classList.contains('blink-off')).toBe true
|
||||
expect(cursorNode2.classList.contains('blink-off')).toBe true
|
||||
|
||||
# Stop blinking immediately when cursors move
|
||||
advanceClock(component.props.cursorBlinkPeriod / 4)
|
||||
expect(cursorNode1.classList.contains('blink-off')).toBe true
|
||||
expect(cursorNode2.classList.contains('blink-off')).toBe true
|
||||
|
||||
# Stop blinking for one full period after moving the cursor
|
||||
editor.moveCursorRight()
|
||||
expect(cursorNode1.classList.contains('blink-off')).toBe false
|
||||
expect(cursorNode2.classList.contains('blink-off')).toBe false
|
||||
|
||||
advanceClock(component.props.cursorBlinkResumeDelay / 2)
|
||||
expect(cursorNode1.classList.contains('blink-off')).toBe false
|
||||
expect(cursorNode2.classList.contains('blink-off')).toBe false
|
||||
|
||||
advanceClock(component.props.cursorBlinkResumeDelay / 2)
|
||||
expect(cursorNode1.classList.contains('blink-off')).toBe true
|
||||
expect(cursorNode2.classList.contains('blink-off')).toBe true
|
||||
|
||||
advanceClock(component.props.cursorBlinkPeriod / 2)
|
||||
expect(cursorNode1.classList.contains('blink-off')).toBe false
|
||||
expect(cursorNode2.classList.contains('blink-off')).toBe false
|
||||
|
||||
it "renders the hidden input field at the position of the last cursor if it is on screen", ->
|
||||
inputNode = node.querySelector('.hidden-input')
|
||||
node.style.height = 5 * lineHeightInPixels + 'px'
|
||||
node.style.width = 10 * charWidth + 'px'
|
||||
component.measureHeightAndWidth()
|
||||
|
||||
expect(editor.getCursorScreenPosition()).toEqual [0, 0]
|
||||
editor.setScrollTop(3 * lineHeightInPixels)
|
||||
editor.setScrollLeft(3 * charWidth)
|
||||
expect(inputNode.offsetTop).toBe 0
|
||||
expect(inputNode.offsetLeft).toBe 0
|
||||
|
||||
editor.setCursorBufferPosition([5, 5])
|
||||
cursorRect = editor.getCursor().getPixelRect()
|
||||
cursorTop = cursorRect.top
|
||||
cursorLeft = cursorRect.left
|
||||
expect(inputNode.offsetTop).toBe cursorTop - editor.getScrollTop()
|
||||
expect(inputNode.offsetLeft).toBe cursorLeft - editor.getScrollLeft()
|
||||
|
||||
it "does not render cursors that are associated with non-empty selections", ->
|
||||
editor.setSelectedScreenRange([[0, 4], [4, 6]])
|
||||
editor.addCursorAtScreenPosition([6, 8])
|
||||
|
||||
cursorNodes = node.querySelectorAll('.cursor')
|
||||
expect(cursorNodes.length).toBe 1
|
||||
expect(cursorNodes[0].offsetTop).toBe 6 * lineHeightInPixels
|
||||
expect(cursorNodes[0].offsetLeft).toBe 8 * charWidth
|
||||
|
||||
describe "selection rendering", ->
|
||||
scrollViewClientLeft = null
|
||||
|
||||
beforeEach ->
|
||||
scrollViewClientLeft = node.querySelector('.scroll-view').getBoundingClientRect().left
|
||||
|
||||
it "renders 1 region for 1-line selections", ->
|
||||
# 1-line selection
|
||||
editor.setSelectedScreenRange([[1, 6], [1, 10]])
|
||||
regions = node.querySelectorAll('.selection .region')
|
||||
|
||||
expect(regions.length).toBe 1
|
||||
regionRect = regions[0].getBoundingClientRect()
|
||||
expect(regionRect.top).toBe 1 * lineHeightInPixels
|
||||
expect(regionRect.height).toBe 1 * lineHeightInPixels
|
||||
expect(regionRect.left).toBe scrollViewClientLeft + 6 * charWidth
|
||||
expect(regionRect.width).toBe 4 * charWidth
|
||||
|
||||
it "renders 2 regions for 2-line selections", ->
|
||||
editor.setSelectedScreenRange([[1, 6], [2, 10]])
|
||||
regions = node.querySelectorAll('.selection .region')
|
||||
expect(regions.length).toBe 2
|
||||
|
||||
region1Rect = regions[0].getBoundingClientRect()
|
||||
expect(region1Rect.top).toBe 1 * lineHeightInPixels
|
||||
expect(region1Rect.height).toBe 1 * lineHeightInPixels
|
||||
expect(region1Rect.left).toBe scrollViewClientLeft + 6 * charWidth
|
||||
expect(Math.ceil(region1Rect.right)).toBe node.clientWidth # TODO: Remove ceiling when react-wrapper is removed
|
||||
|
||||
region2Rect = regions[1].getBoundingClientRect()
|
||||
expect(region2Rect.top).toBe 2 * lineHeightInPixels
|
||||
expect(region2Rect.height).toBe 1 * lineHeightInPixels
|
||||
expect(region2Rect.left).toBe scrollViewClientLeft + 0
|
||||
expect(region2Rect.width).toBe 10 * charWidth
|
||||
|
||||
it "renders 3 regions for selections with more than 2 lines", ->
|
||||
editor.setSelectedScreenRange([[1, 6], [5, 10]])
|
||||
regions = node.querySelectorAll('.selection .region')
|
||||
expect(regions.length).toBe 3
|
||||
|
||||
region1Rect = regions[0].getBoundingClientRect()
|
||||
expect(region1Rect.top).toBe 1 * lineHeightInPixels
|
||||
expect(region1Rect.height).toBe 1 * lineHeightInPixels
|
||||
expect(region1Rect.left).toBe scrollViewClientLeft + 6 * charWidth
|
||||
expect(Math.ceil(region1Rect.right)).toBe node.clientWidth # TODO: Remove ceiling when react-wrapper is removed
|
||||
|
||||
region2Rect = regions[1].getBoundingClientRect()
|
||||
expect(region2Rect.top).toBe 2 * lineHeightInPixels
|
||||
expect(region2Rect.height).toBe 3 * lineHeightInPixels
|
||||
expect(region2Rect.left).toBe scrollViewClientLeft + 0
|
||||
expect(Math.ceil(region2Rect.right)).toBe node.clientWidth # TODO: Remove ceiling when react-wrapper is removed
|
||||
|
||||
region3Rect = regions[2].getBoundingClientRect()
|
||||
expect(region3Rect.top).toBe 5 * lineHeightInPixels
|
||||
expect(region3Rect.height).toBe 1 * lineHeightInPixels
|
||||
expect(region3Rect.left).toBe scrollViewClientLeft + 0
|
||||
expect(region3Rect.width).toBe 10 * charWidth
|
||||
|
||||
it "does not render empty selections", ->
|
||||
expect(editor.getSelection().isEmpty()).toBe true
|
||||
expect(node.querySelectorAll('.selection').length).toBe 0
|
||||
|
||||
describe "mouse interactions", ->
|
||||
linesNode = null
|
||||
|
||||
beforeEach ->
|
||||
delayAnimationFrames = true
|
||||
linesNode = node.querySelector('.lines')
|
||||
|
||||
describe "when a non-folded line is single-clicked", ->
|
||||
describe "when no modifier keys are held down", ->
|
||||
it "moves the cursor to the nearest screen position", ->
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
node.style.width = 10 * charWidth + 'px'
|
||||
component.measureHeightAndWidth()
|
||||
editor.setScrollTop(3.5 * lineHeightInPixels)
|
||||
editor.setScrollLeft(2 * charWidth)
|
||||
|
||||
linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([4, 8])))
|
||||
expect(editor.getCursorScreenPosition()).toEqual [4, 8]
|
||||
|
||||
describe "when the shift key is held down", ->
|
||||
it "selects to the nearest screen position", ->
|
||||
editor.setCursorScreenPosition([3, 4])
|
||||
linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([5, 6]), shiftKey: true))
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[3, 4], [5, 6]]
|
||||
|
||||
describe "when the command key is held down", ->
|
||||
it "adds a cursor at the nearest screen position", ->
|
||||
editor.setCursorScreenPosition([3, 4])
|
||||
linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([5, 6]), metaKey: true))
|
||||
expect(editor.getSelectedScreenRanges()).toEqual [[[3, 4], [3, 4]], [[5, 6], [5, 6]]]
|
||||
|
||||
describe "when a non-folded line is double-clicked", ->
|
||||
it "selects the word containing the nearest screen position", ->
|
||||
linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([5, 10]), detail: 2))
|
||||
linesNode.dispatchEvent(buildMouseEvent('mouseup'))
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[5, 6], [5, 13]]
|
||||
|
||||
linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([6, 6]), detail: 1))
|
||||
linesNode.dispatchEvent(buildMouseEvent('mouseup'))
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[6, 6], [6, 6]]
|
||||
|
||||
linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([8, 8]), detail: 1, shiftKey: true))
|
||||
linesNode.dispatchEvent(buildMouseEvent('mouseup'))
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[6, 6], [8, 8]]
|
||||
|
||||
describe "when a non-folded line is triple-clicked", ->
|
||||
it "selects the line containing the nearest screen position", ->
|
||||
linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([5, 10]), detail: 3))
|
||||
linesNode.dispatchEvent(buildMouseEvent('mouseup'))
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[5, 0], [6, 0]]
|
||||
|
||||
linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([6, 6]), detail: 1, shiftKey: true))
|
||||
linesNode.dispatchEvent(buildMouseEvent('mouseup'))
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[5, 0], [7, 0]]
|
||||
|
||||
linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([7, 5]), detail: 1))
|
||||
linesNode.dispatchEvent(buildMouseEvent('mouseup'))
|
||||
linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([8, 8]), detail: 1, shiftKey: true))
|
||||
linesNode.dispatchEvent(buildMouseEvent('mouseup'))
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[7, 5], [8, 8]]
|
||||
|
||||
describe "when the mouse is clicked and dragged", ->
|
||||
it "selects to the nearest screen position until the mouse button is released", ->
|
||||
linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([2, 4]), which: 1))
|
||||
linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([6, 8]), which: 1))
|
||||
nextAnimationFrame()
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[2, 4], [6, 8]]
|
||||
|
||||
linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([10, 0]), which: 1))
|
||||
nextAnimationFrame()
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[2, 4], [10, 0]]
|
||||
|
||||
linesNode.dispatchEvent(buildMouseEvent('mouseup'))
|
||||
linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([12, 0]), which: 1))
|
||||
nextAnimationFrame()
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[2, 4], [10, 0]]
|
||||
|
||||
it "stops selecting if the mouse is dragged into the dev tools", ->
|
||||
linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([2, 4]), which: 1))
|
||||
linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([6, 8]), which: 1))
|
||||
nextAnimationFrame()
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[2, 4], [6, 8]]
|
||||
|
||||
linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([10, 0]), which: 0))
|
||||
nextAnimationFrame()
|
||||
expect(editor.getSelectedScreenRange()).toEqual [[2, 4], [6, 8]]
|
||||
|
||||
linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([8, 0]), which: 1))
|
||||
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}
|
||||
|
||||
buildMouseEvent = (type, properties...) ->
|
||||
properties = extend({bubbles: true, cancelable: true}, properties...)
|
||||
event = new MouseEvent(type, properties)
|
||||
Object.defineProperty(event, 'which', get: -> properties.which) if properties.which?
|
||||
event
|
||||
|
||||
describe "focus handling", ->
|
||||
inputNode = null
|
||||
|
||||
beforeEach ->
|
||||
inputNode = node.querySelector('.hidden-input')
|
||||
|
||||
it "transfers focus to the hidden input", ->
|
||||
expect(document.activeElement).toBe document.body
|
||||
node.focus()
|
||||
expect(document.activeElement).toBe inputNode
|
||||
|
||||
it "adds the 'is-focused' class to the editor when the hidden input is focused", ->
|
||||
expect(document.activeElement).toBe document.body
|
||||
inputNode.focus()
|
||||
expect(node.classList.contains('is-focused')).toBe true
|
||||
inputNode.blur()
|
||||
expect(node.classList.contains('is-focused')).toBe false
|
||||
|
||||
describe "scrolling", ->
|
||||
it "updates the vertical scrollbar when the scrollTop is changed in the model", ->
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
component.measureHeightAndWidth()
|
||||
|
||||
expect(verticalScrollbarNode.scrollTop).toBe 0
|
||||
|
||||
editor.setScrollTop(10)
|
||||
expect(verticalScrollbarNode.scrollTop).toBe 10
|
||||
|
||||
it "updates the horizontal scrollbar and scroll view content x transform based on the scrollLeft of the model", ->
|
||||
node.style.width = 30 * charWidth + 'px'
|
||||
component.measureHeightAndWidth()
|
||||
|
||||
scrollViewContentNode = node.querySelector('.scroll-view-content')
|
||||
expect(scrollViewContentNode.style['-webkit-transform']).toBe "translate3d(0px, 0px, 0)"
|
||||
expect(horizontalScrollbarNode.scrollLeft).toBe 0
|
||||
|
||||
editor.setScrollLeft(100)
|
||||
expect(scrollViewContentNode.style['-webkit-transform']).toBe "translate3d(-100px, 0px, 0)"
|
||||
expect(horizontalScrollbarNode.scrollLeft).toBe 100
|
||||
|
||||
it "updates the scrollLeft of the model when the scrollLeft of the horizontal scrollbar changes", ->
|
||||
node.style.width = 30 * charWidth + 'px'
|
||||
component.measureHeightAndWidth()
|
||||
|
||||
expect(editor.getScrollLeft()).toBe 0
|
||||
horizontalScrollbarNode.scrollLeft = 100
|
||||
horizontalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
|
||||
|
||||
expect(editor.getScrollLeft()).toBe 100
|
||||
|
||||
describe "when a mousewheel event occurs on the editor", ->
|
||||
it "updates the horizontal or vertical scrollbar depending on which delta is greater (x or y)", ->
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
node.style.width = 20 * charWidth + 'px'
|
||||
component.measureHeightAndWidth()
|
||||
|
||||
expect(verticalScrollbarNode.scrollTop).toBe 0
|
||||
expect(horizontalScrollbarNode.scrollLeft).toBe 0
|
||||
|
||||
node.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -5, wheelDeltaY: -10))
|
||||
expect(verticalScrollbarNode.scrollTop).toBe 10
|
||||
expect(horizontalScrollbarNode.scrollLeft).toBe 0
|
||||
|
||||
node.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -15, wheelDeltaY: -5))
|
||||
expect(verticalScrollbarNode.scrollTop).toBe 10
|
||||
expect(horizontalScrollbarNode.scrollLeft).toBe 15
|
||||
|
||||
describe "input events", ->
|
||||
inputNode = null
|
||||
|
||||
beforeEach ->
|
||||
inputNode = node.querySelector('.hidden-input')
|
||||
|
||||
it "inserts the newest character in the input's value into the buffer", ->
|
||||
inputNode.value = 'x'
|
||||
inputNode.dispatchEvent(new Event('input'))
|
||||
expect(editor.lineForBufferRow(0)).toBe 'xvar quicksort = function () {'
|
||||
|
||||
inputNode.value = 'xy'
|
||||
inputNode.dispatchEvent(new Event('input'))
|
||||
expect(editor.lineForBufferRow(0)).toBe 'xyvar quicksort = function () {'
|
||||
|
||||
it "replaces the last character if the length of the input's value doesn't increase, as occurs with the accented character menu", ->
|
||||
inputNode.value = 'u'
|
||||
inputNode.dispatchEvent(new Event('input'))
|
||||
expect(editor.lineForBufferRow(0)).toBe 'uvar quicksort = function () {'
|
||||
|
||||
inputNode.value = 'ü'
|
||||
inputNode.dispatchEvent(new Event('input'))
|
||||
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", ->
|
||||
spyOn(editor, 'consolidateSelections').andCallThrough()
|
||||
|
||||
event = new CustomEvent('editor:consolidate-selections', bubbles: true, cancelable: true)
|
||||
event.abortKeyBinding = jasmine.createSpy("event.abortKeyBinding")
|
||||
node.dispatchEvent(event)
|
||||
|
||||
expect(editor.consolidateSelections).toHaveBeenCalled()
|
||||
expect(event.abortKeyBinding).toHaveBeenCalled()
|
||||
+130
-2
@@ -111,6 +111,21 @@ describe "Editor", ->
|
||||
editor.moveCursorDown()
|
||||
expect(editor.getCursorBufferPosition()).toEqual [1, 1]
|
||||
|
||||
it "emits a single 'cursors-moved' event for all moved cursors", ->
|
||||
editor.on 'cursors-moved', cursorsMovedHandler = jasmine.createSpy("cursorsMovedHandler")
|
||||
|
||||
editor.moveCursorDown()
|
||||
expect(cursorsMovedHandler.callCount).toBe 1
|
||||
|
||||
cursorsMovedHandler.reset()
|
||||
editor.addCursorAtScreenPosition([3, 0])
|
||||
editor.moveCursorDown()
|
||||
expect(cursorsMovedHandler.callCount).toBe 1
|
||||
|
||||
cursorsMovedHandler.reset()
|
||||
editor.getCursor().moveDown()
|
||||
expect(cursorsMovedHandler.callCount).toBe 1
|
||||
|
||||
describe ".setCursorScreenPosition(screenPosition)", ->
|
||||
it "clears a goal column established by vertical movement", ->
|
||||
# set a goal column by moving down
|
||||
@@ -645,6 +660,67 @@ describe "Editor", ->
|
||||
cursor2 = editor.addCursorAtBufferPosition([1,4])
|
||||
expect(cursor2.marker).toBe cursor1.marker
|
||||
|
||||
describe "autoscroll", ->
|
||||
beforeEach ->
|
||||
editor.manageScrollPosition = true
|
||||
editor.setVerticalScrollMargin(2)
|
||||
editor.setHorizontalScrollMargin(2)
|
||||
editor.setLineHeight(10)
|
||||
editor.setDefaultCharWidth(10)
|
||||
editor.setHeight(5.5 * 10)
|
||||
editor.setWidth(5.5 * 10)
|
||||
|
||||
it "scrolls down when the last cursor gets closer than ::verticalScrollMargin to the bottom of the editor", ->
|
||||
expect(editor.getScrollTop()).toBe 0
|
||||
expect(editor.getScrollBottom()).toBe 5.5 * 10
|
||||
|
||||
editor.setCursorScreenPosition([2, 0])
|
||||
expect(editor.getScrollBottom()).toBe 5.5 * 10
|
||||
|
||||
editor.moveCursorDown()
|
||||
expect(editor.getScrollBottom()).toBe 6 * 10
|
||||
|
||||
editor.moveCursorDown()
|
||||
expect(editor.getScrollBottom()).toBe 7 * 10
|
||||
|
||||
it "scrolls up when the last cursor gets closer than ::verticalScrollMargin to the top of the editor", ->
|
||||
editor.setCursorScreenPosition([11, 0])
|
||||
editor.setScrollBottom(editor.getScrollHeight())
|
||||
|
||||
editor.moveCursorUp()
|
||||
expect(editor.getScrollBottom()).toBe editor.getScrollHeight()
|
||||
|
||||
editor.moveCursorUp()
|
||||
expect(editor.getScrollTop()).toBe 7 * 10
|
||||
|
||||
editor.moveCursorUp()
|
||||
expect(editor.getScrollTop()).toBe 6 * 10
|
||||
|
||||
it "scrolls right when the last cursor gets closer than ::horizontalScrollMargin to the right of the editor", ->
|
||||
expect(editor.getScrollLeft()).toBe 0
|
||||
expect(editor.getScrollRight()).toBe 5.5 * 10
|
||||
|
||||
editor.setCursorScreenPosition([0, 2])
|
||||
expect(editor.getScrollRight()).toBe 5.5 * 10
|
||||
|
||||
editor.moveCursorRight()
|
||||
expect(editor.getScrollRight()).toBe 6 * 10
|
||||
|
||||
editor.moveCursorRight()
|
||||
expect(editor.getScrollRight()).toBe 7 * 10
|
||||
|
||||
it "scrolls left when the last cursor gets closer than ::horizontalScrollMargin to the left of the editor", ->
|
||||
editor.setScrollRight(editor.getScrollWidth())
|
||||
editor.setCursorScreenPosition([6, 62])
|
||||
|
||||
expect(editor.getScrollRight()).toBe editor.getScrollWidth()
|
||||
|
||||
editor.moveCursorLeft()
|
||||
expect(editor.getScrollLeft()).toBe 59 * 10
|
||||
|
||||
editor.moveCursorLeft()
|
||||
expect(editor.getScrollLeft()).toBe 58 * 10
|
||||
|
||||
describe "selection", ->
|
||||
selection = null
|
||||
|
||||
@@ -1000,7 +1076,7 @@ describe "Editor", ->
|
||||
expect(selection1).toBe selection
|
||||
expect(selection1.getBufferRange()).toEqual [[2, 2], [3, 3]]
|
||||
|
||||
describe "when the preserveFolds option is false (the default)", ->
|
||||
describe "when the 'preserveFolds' option is false (the default)", ->
|
||||
it "removes folds that contain the selections", ->
|
||||
editor.setSelectedBufferRange([[0,0], [0,0]])
|
||||
editor.createFold(1, 4)
|
||||
@@ -1014,7 +1090,7 @@ describe "Editor", ->
|
||||
expect(editor.lineForScreenRow(6).fold).toBeUndefined()
|
||||
expect(editor.lineForScreenRow(10).fold).toBeDefined()
|
||||
|
||||
describe "when the preserve folds option is true", ->
|
||||
describe "when the 'preserveFolds' option is true", ->
|
||||
it "does not remove folds that contain the selections", ->
|
||||
editor.setSelectedBufferRange([[0,0], [0,0]])
|
||||
editor.createFold(1, 4)
|
||||
@@ -1023,6 +1099,24 @@ describe "Editor", ->
|
||||
expect(editor.isFoldedAtBufferRow(1)).toBeTruthy()
|
||||
expect(editor.isFoldedAtBufferRow(6)).toBeTruthy()
|
||||
|
||||
describe ".setSelectedBufferRange(range)", ->
|
||||
describe "when the 'autoscroll' option is true", ->
|
||||
it "autoscrolls to the selection", ->
|
||||
editor.manageScrollPosition = true
|
||||
editor.setLineHeight(10)
|
||||
editor.setDefaultCharWidth(10)
|
||||
editor.setHeight(50)
|
||||
editor.setWidth(50)
|
||||
expect(editor.getScrollTop()).toBe 0
|
||||
|
||||
editor.setSelectedBufferRange([[5, 6], [6, 8]], autoscroll: true)
|
||||
expect(editor.getScrollBottom()).toBe (7 + editor.getVerticalScrollMargin()) * 10
|
||||
expect(editor.getScrollRight()).toBe 50
|
||||
|
||||
editor.setSelectedBufferRange([[6, 6], [6, 8]], autoscroll: true)
|
||||
expect(editor.getScrollBottom()).toBe (7 + editor.getVerticalScrollMargin()) * 10
|
||||
expect(editor.getScrollRight()).toBe (8 + editor.getHorizontalScrollMargin()) * 10
|
||||
|
||||
describe ".selectMarker(marker)", ->
|
||||
describe "if the marker is valid", ->
|
||||
it "selects the marker's range and returns the selected range", ->
|
||||
@@ -2934,3 +3028,37 @@ describe "Editor", ->
|
||||
editor.setSoftTabs(false)
|
||||
editor.normalizeTabsInBufferRange([[0, 0], [Infinity, Infinity]])
|
||||
expect(editor.getText()).toBe ' '
|
||||
|
||||
describe ".scrollToCursorPosition()", ->
|
||||
it "scrolls the last cursor into view", ->
|
||||
editor.setCursorScreenPosition([8, 8])
|
||||
editor.setLineHeight(10)
|
||||
editor.setDefaultCharWidth(10)
|
||||
editor.setHeight(50)
|
||||
editor.setWidth(50)
|
||||
expect(editor.getScrollTop()).toBe 0
|
||||
expect(editor.getScrollLeft()).toBe 0
|
||||
|
||||
editor.scrollToCursorPosition()
|
||||
expect(editor.getScrollBottom()).toBe (9 + editor.getVerticalScrollMargin()) * 10
|
||||
expect(editor.getScrollRight()).toBe (9 + editor.getHorizontalScrollMargin()) * 10
|
||||
|
||||
describe ".pageUp/Down()", ->
|
||||
it "scrolls one screen height up or down", ->
|
||||
editor.manageScrollPosition = true
|
||||
|
||||
editor.setLineHeight(10)
|
||||
editor.setHeight(50)
|
||||
expect(editor.getScrollHeight()).toBe 130
|
||||
|
||||
editor.pageDown()
|
||||
expect(editor.getScrollTop()).toBe 50
|
||||
|
||||
editor.pageDown()
|
||||
expect(editor.getScrollTop()).toBe 80
|
||||
|
||||
editor.pageUp()
|
||||
expect(editor.getScrollTop()).toBe 30
|
||||
|
||||
editor.pageUp()
|
||||
expect(editor.getScrollTop()).toBe 0
|
||||
|
||||
@@ -1385,7 +1385,7 @@ describe "EditorView", ->
|
||||
expect(editorView.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(6)
|
||||
expect(editorView.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(12)
|
||||
|
||||
buffer.change([[1,0], [3,0]], "1\n2\n3\n")
|
||||
buffer.setTextInRange([[1,0], [3,0]], "1\n2\n3\n")
|
||||
expect(editorView.renderedLines.find(".line").length).toBe 7
|
||||
expect(editorView.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(6)
|
||||
expect(editorView.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(12)
|
||||
@@ -1398,21 +1398,21 @@ describe "EditorView", ->
|
||||
expect(editorView.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(6)
|
||||
expect(editorView.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(12)
|
||||
|
||||
buffer.change([[2,0], [7,0]], "2\n3\n4\n5\n6\n7\n8\n9\n")
|
||||
buffer.setTextInRange([[2,0], [7,0]], "2\n3\n4\n5\n6\n7\n8\n9\n")
|
||||
expect(editorView.renderedLines.find(".line").length).toBe 7
|
||||
expect(editorView.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(6)
|
||||
expect(editorView.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(12)
|
||||
|
||||
describe "when the change straddles the last rendered row", ->
|
||||
it "doesn't render rows that were not previously rendered", ->
|
||||
buffer.change([[2,0], [7,0]], "2\n3\n4\n5\n6\n7\n8\n")
|
||||
buffer.setTextInRange([[2,0], [7,0]], "2\n3\n4\n5\n6\n7\n8\n")
|
||||
expect(editorView.renderedLines.find(".line").length).toBe 7
|
||||
expect(editorView.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(0)
|
||||
expect(editorView.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(6)
|
||||
|
||||
describe "when the change the follows the last rendered row", ->
|
||||
it "does not change the rendered lines", ->
|
||||
buffer.change([[12,0], [12,0]], "12\n13\n14\n")
|
||||
buffer.setTextInRange([[12,0], [12,0]], "12\n13\n14\n")
|
||||
expect(editorView.renderedLines.find(".line").length).toBe 7
|
||||
expect(editorView.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(0)
|
||||
expect(editorView.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(6)
|
||||
@@ -1422,7 +1422,7 @@ describe "EditorView", ->
|
||||
setEditorWidthInChars(editorView, maxLineLength)
|
||||
widthBefore = editorView.renderedLines.width()
|
||||
expect(widthBefore).toBe editorView.scrollView.width() + 20
|
||||
buffer.change([[12,0], [12,0]], [1..maxLineLength*2].join(''))
|
||||
buffer.setTextInRange([[12,0], [12,0]], [1..maxLineLength*2].join(''))
|
||||
expect(editorView.renderedLines.width()).toBeGreaterThan widthBefore
|
||||
|
||||
describe "when lines are removed", ->
|
||||
@@ -1432,7 +1432,7 @@ describe "EditorView", ->
|
||||
it "sets the rendered screen line's width to either the max line length or the scollView's width (whichever is greater)", ->
|
||||
maxLineLength = editor.getMaxScreenLineLength()
|
||||
setEditorWidthInChars(editorView, maxLineLength)
|
||||
buffer.change([[12,0], [12,0]], [1..maxLineLength*2].join(''))
|
||||
buffer.setTextInRange([[12,0], [12,0]], [1..maxLineLength*2].join(''))
|
||||
expect(editorView.renderedLines.width()).toBeGreaterThan editorView.scrollView.width()
|
||||
widthBefore = editorView.renderedLines.width()
|
||||
buffer.delete([[12, 0], [12, Infinity]])
|
||||
@@ -1445,7 +1445,7 @@ describe "EditorView", ->
|
||||
expect(editorView.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(6)
|
||||
expect(editorView.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(12)
|
||||
|
||||
buffer.change([[1,0], [2,0]], "")
|
||||
buffer.setTextInRange([[1,0], [2,0]], "")
|
||||
expect(editorView.renderedLines.find(".line").length).toBe 6
|
||||
expect(editorView.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(6)
|
||||
expect(editorView.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(11)
|
||||
@@ -1457,21 +1457,21 @@ describe "EditorView", ->
|
||||
expect(editorView.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(6)
|
||||
expect(editorView.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(12)
|
||||
|
||||
buffer.change([[7,0], [11,0]], "1\n2\n")
|
||||
buffer.setTextInRange([[7,0], [11,0]], "1\n2\n")
|
||||
expect(editorView.renderedLines.find(".line").length).toBe 5
|
||||
expect(editorView.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(6)
|
||||
expect(editorView.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(10)
|
||||
|
||||
describe "when the change straddles the last rendered row", ->
|
||||
it "renders the correct rows", ->
|
||||
buffer.change([[2,0], [7,0]], "")
|
||||
buffer.setTextInRange([[2,0], [7,0]], "")
|
||||
expect(editorView.renderedLines.find(".line").length).toBe 7
|
||||
expect(editorView.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(0)
|
||||
expect(editorView.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(6)
|
||||
|
||||
describe "when the change the follows the last rendered row", ->
|
||||
it "does not change the rendered lines", ->
|
||||
buffer.change([[10,0], [12,0]], "")
|
||||
buffer.setTextInRange([[10,0], [12,0]], "")
|
||||
expect(editorView.renderedLines.find(".line").length).toBe 7
|
||||
expect(editorView.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(0)
|
||||
expect(editorView.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(6)
|
||||
@@ -1596,7 +1596,7 @@ describe "EditorView", ->
|
||||
editor.setSoftWrap(true)
|
||||
|
||||
it "doesn't show the end of line invisible at the end of lines broken due to wrapping", ->
|
||||
editor.setText "a line that wraps"
|
||||
editor.setText "a line that wraps "
|
||||
editorView.attachToDom()
|
||||
editorView.setWidthInChars(6)
|
||||
atom.config.set "editor.showInvisibles", true
|
||||
@@ -1604,11 +1604,11 @@ describe "EditorView", ->
|
||||
expect(space).toBeTruthy()
|
||||
eol = editorView.invisibles?.eol
|
||||
expect(eol).toBeTruthy()
|
||||
expect(editorView.renderedLines.find('.line:first').text()).toBe "a line#{space}"
|
||||
expect(editorView.renderedLines.find('.line:last').text()).toBe "wraps#{eol}"
|
||||
expect(editorView.renderedLines.find('.line:first').text()).toBe "a line "
|
||||
expect(editorView.renderedLines.find('.line:last').text()).toBe "wraps#{space}#{eol}"
|
||||
|
||||
it "displays trailing carriage return using a visible non-empty value", ->
|
||||
editor.setText "a line that\r\n"
|
||||
editor.setText "a line that \r\n"
|
||||
editorView.attachToDom()
|
||||
editorView.setWidthInChars(6)
|
||||
atom.config.set "editor.showInvisibles", true
|
||||
@@ -1618,8 +1618,8 @@ describe "EditorView", ->
|
||||
expect(cr).toBeTruthy()
|
||||
eol = editorView.invisibles?.eol
|
||||
expect(eol).toBeTruthy()
|
||||
expect(editorView.renderedLines.find('.line:first').text()).toBe "a line#{space}"
|
||||
expect(editorView.renderedLines.find('.line:eq(1)').text()).toBe "that#{cr}#{eol}"
|
||||
expect(editorView.renderedLines.find('.line:first').text()).toBe "a line "
|
||||
expect(editorView.renderedLines.find('.line:eq(1)').text()).toBe "that#{space}#{cr}#{eol}"
|
||||
expect(editorView.renderedLines.find('.line:last').text()).toBe "#{eol}"
|
||||
|
||||
describe "when editor.showIndentGuide is set to true", ->
|
||||
|
||||
@@ -188,17 +188,17 @@ describe "LanguageMode", ->
|
||||
expect(buffer.lineForRow(3)).toBe " font-weight: bold !important;"
|
||||
|
||||
it "uncomments lines with leading whitespace", ->
|
||||
buffer.change([[2, 0], [2, Infinity]], " /*width: 110%;*/")
|
||||
buffer.setTextInRange([[2, 0], [2, Infinity]], " /*width: 110%;*/")
|
||||
languageMode.toggleLineCommentsForBufferRows(2, 2)
|
||||
expect(buffer.lineForRow(2)).toBe " width: 110%;"
|
||||
|
||||
it "uncomments lines with trailing whitespace", ->
|
||||
buffer.change([[2, 0], [2, Infinity]], "/*width: 110%;*/ ")
|
||||
buffer.setTextInRange([[2, 0], [2, Infinity]], "/*width: 110%;*/ ")
|
||||
languageMode.toggleLineCommentsForBufferRows(2, 2)
|
||||
expect(buffer.lineForRow(2)).toBe "width: 110%; "
|
||||
|
||||
it "uncomments lines with leading and trailing whitespace", ->
|
||||
buffer.change([[2, 0], [2, Infinity]], " /*width: 110%;*/ ")
|
||||
buffer.setTextInRange([[2, 0], [2, Infinity]], " /*width: 110%;*/ ")
|
||||
languageMode.toggleLineCommentsForBufferRows(2, 2)
|
||||
expect(buffer.lineForRow(2)).toBe " width: 110%; "
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ describe "PaneContainerView", ->
|
||||
afterEach ->
|
||||
atom.deserializers.remove(TestView)
|
||||
|
||||
describe ".getActivePane()", ->
|
||||
describe ".getActivePaneView()", ->
|
||||
it "returns the most-recently focused pane", ->
|
||||
focusStealer = $$ -> @div tabindex: -1, "focus stealer"
|
||||
focusStealer.attachToDom()
|
||||
@@ -35,15 +35,15 @@ describe "PaneContainerView", ->
|
||||
|
||||
pane2.focus()
|
||||
expect(container.getFocusedPane()).toBe pane2
|
||||
expect(container.getActivePane()).toBe pane2
|
||||
expect(container.getActivePaneView()).toBe pane2
|
||||
|
||||
focusStealer.focus()
|
||||
expect(container.getFocusedPane()).toBeUndefined()
|
||||
expect(container.getActivePane()).toBe pane2
|
||||
expect(container.getActivePaneView()).toBe pane2
|
||||
|
||||
pane3.focus()
|
||||
expect(container.getFocusedPane()).toBe pane3
|
||||
expect(container.getActivePane()).toBe pane3
|
||||
expect(container.getActivePaneView()).toBe pane3
|
||||
|
||||
describe ".eachPaneView(callback)", ->
|
||||
it "runs the callback with all current and future panes until the subscription is cancelled", ->
|
||||
|
||||
@@ -270,7 +270,7 @@ describe "Project", ->
|
||||
|
||||
it "does NOT save when modified", ->
|
||||
editor = atom.project.openSync('sample.js')
|
||||
editor.buffer.change([[0,0],[0,0]], 'omg')
|
||||
editor.buffer.setTextInRange([[0,0],[0,0]], 'omg')
|
||||
expect(editor.isModified()).toBeTruthy()
|
||||
|
||||
results = []
|
||||
|
||||
@@ -84,6 +84,7 @@ beforeEach ->
|
||||
config.set "editor.autoIndent", false
|
||||
config.set "core.disabledPackages", ["package-that-throws-an-exception",
|
||||
"package-with-broken-package-json", "package-with-broken-keymap"]
|
||||
config.set "core.useReactEditor", false
|
||||
config.save.reset()
|
||||
atom.config = config
|
||||
|
||||
@@ -243,6 +244,15 @@ window.fakeSetTimeout = (callback, ms) ->
|
||||
window.fakeClearTimeout = (idToClear) ->
|
||||
window.timeouts = window.timeouts.filter ([id]) -> id != idToClear
|
||||
|
||||
window.fakeSetInterval = (callback, ms) ->
|
||||
action = ->
|
||||
callback()
|
||||
window.fakeSetTimeout(action, ms)
|
||||
window.fakeSetTimeout(action, ms)
|
||||
|
||||
window.fakeClearInterval = (idToClear) ->
|
||||
window.fakeClearTimeout(idToClear)
|
||||
|
||||
window.advanceClock = (delta=1) ->
|
||||
window.now += delta
|
||||
callbacks = []
|
||||
|
||||
@@ -36,6 +36,22 @@ describe "ThemeManager", ->
|
||||
themes = themeManager.getActiveThemes()
|
||||
expect(themes).toHaveLength(names.length)
|
||||
|
||||
describe "when the core.themes config value contains invalid entry", ->
|
||||
it "ignores theme", ->
|
||||
atom.config.set 'core.themes', [
|
||||
'atom-light-ui'
|
||||
null
|
||||
undefined
|
||||
''
|
||||
false
|
||||
4
|
||||
{}
|
||||
[]
|
||||
'atom-dark-ui'
|
||||
]
|
||||
|
||||
expect(themeManager.getEnabledThemeNames()).toEqual ['atom-dark-ui', 'atom-light-ui']
|
||||
|
||||
describe "getImportPaths()", ->
|
||||
it "returns the theme directories before the themes are loaded", ->
|
||||
atom.config.set('core.themes', ['theme-with-index-less', 'atom-dark-ui', 'atom-light-ui'])
|
||||
|
||||
@@ -119,7 +119,7 @@ describe "TokenizedBuffer", ->
|
||||
|
||||
describe "when there is a buffer change surrounding an invalid row", ->
|
||||
it "pushes the invalid row to the end of the change", ->
|
||||
buffer.change([[4, 0], [6, 0]], "\n\n\n")
|
||||
buffer.setTextInRange([[4, 0], [6, 0]], "\n\n\n")
|
||||
changeHandler.reset()
|
||||
|
||||
expect(tokenizedBuffer.firstInvalidRow()).toBe 8
|
||||
@@ -128,7 +128,7 @@ describe "TokenizedBuffer", ->
|
||||
describe "when there is a buffer change inside an invalid region", ->
|
||||
it "does not attempt to tokenize the lines in the change, and preserves the existing invalid row", ->
|
||||
expect(tokenizedBuffer.firstInvalidRow()).toBe 5
|
||||
buffer.change([[6, 0], [7, 0]], "\n\n\n")
|
||||
buffer.setTextInRange([[6, 0], [7, 0]], "\n\n\n")
|
||||
|
||||
expect(tokenizedBuffer.lineForScreenRow(6).ruleStack?).toBeFalsy()
|
||||
expect(tokenizedBuffer.lineForScreenRow(7).ruleStack?).toBeFalsy()
|
||||
@@ -143,7 +143,7 @@ describe "TokenizedBuffer", ->
|
||||
describe "when there is a buffer change that is smaller than the chunk size", ->
|
||||
describe "when lines are updated, but none are added or removed", ->
|
||||
it "updates tokens to reflect the change", ->
|
||||
buffer.change([[0, 0], [2, 0]], "foo()\n7\n")
|
||||
buffer.setTextInRange([[0, 0], [2, 0]], "foo()\n7\n")
|
||||
|
||||
expect(tokenizedBuffer.lineForScreenRow(0).tokens[1]).toEqual(value: '(', scopes: ['source.js', 'meta.brace.round.js'])
|
||||
expect(tokenizedBuffer.lineForScreenRow(1).tokens[0]).toEqual(value: '7', scopes: ['source.js', 'constant.numeric.js'])
|
||||
@@ -185,7 +185,7 @@ describe "TokenizedBuffer", ->
|
||||
|
||||
describe "when lines are both updated and removed", ->
|
||||
it "updates tokens to reflect the change", ->
|
||||
buffer.change([[1, 0], [3, 0]], "foo()")
|
||||
buffer.setTextInRange([[1, 0], [3, 0]], "foo()")
|
||||
|
||||
# previous line 0 remains
|
||||
expect(tokenizedBuffer.lineForScreenRow(0).tokens[0]).toEqual(value: 'var', scopes: ['source.js', 'storage.modifier.js'])
|
||||
@@ -209,7 +209,7 @@ describe "TokenizedBuffer", ->
|
||||
buffer.insert([5, 30], '/* */')
|
||||
changeHandler.reset()
|
||||
|
||||
buffer.change([[2, 0], [3, 0]], '/*')
|
||||
buffer.setTextInRange([[2, 0], [3, 0]], '/*')
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js']
|
||||
expect(tokenizedBuffer.lineForScreenRow(3).tokens[0].scopes).toEqual ['source.js']
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
@@ -228,7 +228,7 @@ describe "TokenizedBuffer", ->
|
||||
|
||||
describe "when lines are both updated and inserted", ->
|
||||
it "updates tokens to reflect the change", ->
|
||||
buffer.change([[1, 0], [2, 0]], "foo()\nbar()\nbaz()\nquux()")
|
||||
buffer.setTextInRange([[1, 0], [2, 0]], "foo()\nbar()\nbaz()\nquux()")
|
||||
|
||||
# previous line 0 remains
|
||||
expect(tokenizedBuffer.lineForScreenRow(0).tokens[0]).toEqual( value: 'var', scopes: ['source.js', 'storage.modifier.js'])
|
||||
@@ -463,3 +463,77 @@ describe "TokenizedBuffer", ->
|
||||
expect(tokenizedBuffer.tokenForPosition([0,0]).value).toBe ' '
|
||||
atom.config.set('editor.tabLength', 0)
|
||||
expect(tokenizedBuffer.tokenForPosition([0,0]).value).toBe ' '
|
||||
|
||||
describe "leading and trailing whitespace", ->
|
||||
beforeEach ->
|
||||
buffer = atom.project.bufferForPathSync('sample.js')
|
||||
tokenizedBuffer = new TokenizedBuffer({buffer})
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
it "sets ::hasLeadingWhitespace to true on tokens that have leading whitespace", ->
|
||||
expect(tokenizedBuffer.lineForScreenRow(0).tokens[0].hasLeadingWhitespace).toBe false
|
||||
expect(tokenizedBuffer.lineForScreenRow(1).tokens[0].hasLeadingWhitespace).toBe true
|
||||
expect(tokenizedBuffer.lineForScreenRow(1).tokens[1].hasLeadingWhitespace).toBe false
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).tokens[0].hasLeadingWhitespace).toBe true
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).tokens[1].hasLeadingWhitespace).toBe true
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).tokens[2].hasLeadingWhitespace).toBe false
|
||||
|
||||
# The 4th token *has* leading whitespace, but isn't entirely whitespace
|
||||
buffer.insert([5, 0], ' ')
|
||||
expect(tokenizedBuffer.lineForScreenRow(5).tokens[3].hasLeadingWhitespace).toBe true
|
||||
expect(tokenizedBuffer.lineForScreenRow(5).tokens[4].hasLeadingWhitespace).toBe false
|
||||
|
||||
# Lines that are *only* whitespace are not considered to have leading whitespace
|
||||
buffer.insert([10, 0], ' ')
|
||||
expect(tokenizedBuffer.lineForScreenRow(10).tokens[0].hasLeadingWhitespace).toBe false
|
||||
|
||||
it "sets ::hasTrailingWhitespace to true on tokens that have trailing whitespace", ->
|
||||
buffer.insert([0, Infinity], ' ')
|
||||
expect(tokenizedBuffer.lineForScreenRow(0).tokens[11].hasTrailingWhitespace).toBe false
|
||||
expect(tokenizedBuffer.lineForScreenRow(0).tokens[12].hasTrailingWhitespace).toBe true
|
||||
|
||||
# The last token *has* trailing whitespace, but isn't entirely whitespace
|
||||
buffer.setTextInRange([[2, 39], [2, 40]], ' ')
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).tokens[14].hasTrailingWhitespace).toBe false
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).tokens[15].hasTrailingWhitespace).toBe true
|
||||
|
||||
# Lines that are *only* whitespace are considered to have trailing whitespace
|
||||
buffer.insert([10, 0], ' ')
|
||||
expect(tokenizedBuffer.lineForScreenRow(10).tokens[0].hasTrailingWhitespace).toBe true
|
||||
|
||||
it "only marks trailing whitespace on the last segment of a soft-wrapped line", ->
|
||||
buffer.insert([0, Infinity], ' ')
|
||||
tokenizedLine = tokenizedBuffer.lineForScreenRow(0)
|
||||
[segment1, segment2] = tokenizedLine.softWrapAt(16)
|
||||
expect(segment1.tokens[5].value).toBe ' '
|
||||
expect(segment1.tokens[5].hasTrailingWhitespace).toBe false
|
||||
expect(segment2.tokens[6].value).toBe ' '
|
||||
expect(segment2.tokens[6].hasTrailingWhitespace).toBe true
|
||||
|
||||
describe "indent level", ->
|
||||
beforeEach ->
|
||||
buffer = atom.project.bufferForPathSync('sample.js')
|
||||
tokenizedBuffer = new TokenizedBuffer({buffer})
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
describe "when the line is non-empty", ->
|
||||
it "has an indent level based on the leading whitespace on the line", ->
|
||||
expect(tokenizedBuffer.lineForScreenRow(0).indentLevel).toBe 0
|
||||
expect(tokenizedBuffer.lineForScreenRow(1).indentLevel).toBe 1
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).indentLevel).toBe 2
|
||||
buffer.insert([2, 0], ' ')
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).indentLevel).toBe 2.5
|
||||
|
||||
describe "when the line is empty", ->
|
||||
it "assumes the indentation level of the first non-empty line below or above if one exists", ->
|
||||
buffer.insert([12, 0], ' ')
|
||||
buffer.insert([12, Infinity], '\n\n')
|
||||
expect(tokenizedBuffer.lineForScreenRow(13).indentLevel).toBe 2
|
||||
expect(tokenizedBuffer.lineForScreenRow(14).indentLevel).toBe 2
|
||||
|
||||
buffer.insert([1, Infinity], '\n\n')
|
||||
expect(tokenizedBuffer.lineForScreenRow(2).indentLevel).toBe 2
|
||||
expect(tokenizedBuffer.lineForScreenRow(3).indentLevel).toBe 2
|
||||
|
||||
buffer.setText('\n\n\n')
|
||||
expect(tokenizedBuffer.lineForScreenRow(1).indentLevel).toBe 0
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
describe "TokenizedLine", ->
|
||||
editor = null
|
||||
|
||||
beforeEach ->
|
||||
waitsForPromise -> atom.packages.activatePackage('language-coffee-script')
|
||||
|
||||
describe "::getScopeTree()", ->
|
||||
it "returns a tree whose inner nodes are scopes and whose leaf nodes are tokens in those scopes", ->
|
||||
editor = atom.project.openSync('coffee.coffee')
|
||||
|
||||
ensureValidScopeTree = (scopeTree, scopes=[]) ->
|
||||
if scopeTree.children?
|
||||
for child in scopeTree.children
|
||||
ensureValidScopeTree(child, scopes.concat([scopeTree.scope]))
|
||||
else
|
||||
expect(scopeTree).toBe tokens[tokenIndex++]
|
||||
expect(scopes).toEqual scopeTree.scopes
|
||||
|
||||
tokenIndex = 0
|
||||
tokens = editor.lineForScreenRow(1).tokens
|
||||
scopeTree = editor.lineForScreenRow(1).getScopeTree()
|
||||
ensureValidScopeTree(scopeTree)
|
||||
@@ -102,7 +102,7 @@ describe "Window", ->
|
||||
it "unsubscribes from all buffers", ->
|
||||
atom.workspaceView.openSync('sample.js')
|
||||
buffer = atom.workspaceView.getActivePaneItem().buffer
|
||||
pane = atom.workspaceView.getActivePane()
|
||||
pane = atom.workspaceView.getActivePaneView()
|
||||
pane.splitRight(pane.copyActiveItem())
|
||||
expect(atom.workspaceView.find('.editor').length).toBe 2
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
Q = require 'q'
|
||||
path = require 'path'
|
||||
temp = require 'temp'
|
||||
EditorView = require '../src/editor-view'
|
||||
PaneView = require '../src/pane-view'
|
||||
Workspace = require '../src/workspace'
|
||||
|
||||
@@ -36,18 +37,18 @@ describe "WorkspaceView", ->
|
||||
editorView1 = atom.workspaceView.getActiveView()
|
||||
buffer = editorView1.getEditor().getBuffer()
|
||||
editorView1.splitRight()
|
||||
expect(atom.workspaceView.getActivePane()).toBe atom.workspaceView.getPaneViews()[1]
|
||||
expect(atom.workspaceView.getActivePaneView()).toBe atom.workspaceView.getPaneViews()[1]
|
||||
|
||||
simulateReload()
|
||||
|
||||
expect(atom.workspaceView.getEditorViews().length).toBe 2
|
||||
expect(atom.workspaceView.getActivePane()).toBe atom.workspaceView.getPaneViews()[1]
|
||||
expect(atom.workspaceView.getActivePaneView()).toBe atom.workspaceView.getPaneViews()[1]
|
||||
expect(atom.workspaceView.title).toBe "untitled - #{atom.project.getPath()}"
|
||||
|
||||
describe "when there are open editors", ->
|
||||
it "constructs the view with the same panes", ->
|
||||
atom.workspaceView.attachToDom()
|
||||
pane1 = atom.workspaceView.getActivePane()
|
||||
pane1 = atom.workspaceView.getActivePaneView()
|
||||
pane2 = pane1.splitRight()
|
||||
pane3 = pane2.splitRight()
|
||||
pane4 = pane2.splitDown()
|
||||
@@ -89,7 +90,7 @@ describe "WorkspaceView", ->
|
||||
|
||||
describe "where there are no open editors", ->
|
||||
it "constructs the view with no open editors", ->
|
||||
atom.workspaceView.getActivePane().remove()
|
||||
atom.workspaceView.getActivePaneView().remove()
|
||||
expect(atom.workspaceView.getEditorViews().length).toBe 0
|
||||
simulateReload()
|
||||
expect(atom.workspaceView.getEditorViews().length).toBe 0
|
||||
@@ -99,7 +100,7 @@ describe "WorkspaceView", ->
|
||||
atom.workspaceView.attachToDom()
|
||||
|
||||
it "hands off focus to the active pane", ->
|
||||
activePane = atom.workspaceView.getActivePane()
|
||||
activePane = atom.workspaceView.getActivePaneView()
|
||||
$('body').focus()
|
||||
expect(activePane.hasFocus()).toBe false
|
||||
atom.workspaceView.focus()
|
||||
@@ -132,30 +133,30 @@ describe "WorkspaceView", ->
|
||||
|
||||
describe "when there is an active pane item", ->
|
||||
it "sets the title to the pane item's title plus the project path", ->
|
||||
item = atom.workspaceView.getActivePaneItem()
|
||||
item = atom.workspace.getActivePaneItem()
|
||||
expect(atom.workspaceView.title).toBe "#{item.getTitle()} - #{atom.project.getPath()}"
|
||||
|
||||
describe "when the title of the active pane item changes", ->
|
||||
it "updates the window title based on the item's new title", ->
|
||||
editor = atom.workspaceView.getActivePaneItem()
|
||||
editor = atom.workspace.getActivePaneItem()
|
||||
editor.buffer.setPath(path.join(temp.dir, 'hi'))
|
||||
expect(atom.workspaceView.title).toBe "#{editor.getTitle()} - #{atom.project.getPath()}"
|
||||
|
||||
describe "when the active pane's item changes", ->
|
||||
it "updates the title to the new item's title plus the project path", ->
|
||||
atom.workspaceView.getActivePane().showNextItem()
|
||||
item = atom.workspaceView.getActivePaneItem()
|
||||
atom.workspaceView.getActivePaneView().showNextItem()
|
||||
item = atom.workspace.getActivePaneItem()
|
||||
expect(atom.workspaceView.title).toBe "#{item.getTitle()} - #{atom.project.getPath()}"
|
||||
|
||||
describe "when the last pane item is removed", ->
|
||||
it "updates the title to contain the project's path", ->
|
||||
atom.workspaceView.getActivePane().remove()
|
||||
expect(atom.workspaceView.getActivePaneItem()).toBeUndefined()
|
||||
atom.workspaceView.getActivePaneView().remove()
|
||||
expect(atom.workspace.getActivePaneItem()).toBeUndefined()
|
||||
expect(atom.workspaceView.title).toBe atom.project.getPath()
|
||||
|
||||
describe "when an inactive pane's item changes", ->
|
||||
it "does not update the title", ->
|
||||
pane = atom.workspaceView.getActivePane()
|
||||
pane = atom.workspaceView.getActivePaneView()
|
||||
pane.splitRight()
|
||||
initialTitle = atom.workspaceView.title
|
||||
pane.showNextItem()
|
||||
@@ -164,7 +165,7 @@ describe "WorkspaceView", ->
|
||||
describe "when the root view is deserialized", ->
|
||||
it "updates the title to contain the project's path", ->
|
||||
workspaceView2 = new WorkspaceView(atom.workspace.testSerialization())
|
||||
item = atom.workspaceView.getActivePaneItem()
|
||||
item = atom.workspace.getActivePaneItem()
|
||||
expect(workspaceView2.title).toBe "#{item.getTitle()} - #{atom.project.getPath()}"
|
||||
workspaceView2.remove()
|
||||
|
||||
@@ -222,6 +223,14 @@ describe "WorkspaceView", ->
|
||||
expect(count).toBe 1
|
||||
expect(callbackEditor).toBe atom.workspaceView.getActiveView()
|
||||
|
||||
it "does not invoke the callback for mini editors", ->
|
||||
editorViewCreatedHandler = jasmine.createSpy('editorViewCreatedHandler')
|
||||
atom.workspaceView.eachEditorView(editorViewCreatedHandler)
|
||||
editorViewCreatedHandler.reset()
|
||||
miniEditor = new EditorView(mini: true)
|
||||
atom.workspaceView.append(miniEditor)
|
||||
expect(editorViewCreatedHandler).not.toHaveBeenCalled()
|
||||
|
||||
it "returns a subscription that can be disabled", ->
|
||||
count = 0
|
||||
callback = (editor) -> count++
|
||||
@@ -238,9 +247,9 @@ describe "WorkspaceView", ->
|
||||
it "closes the active pane item until all that remains is a single empty pane", ->
|
||||
atom.config.set('core.destroyEmptyPanes', true)
|
||||
atom.project.openSync('../sample.txt')
|
||||
expect(atom.workspaceView.getActivePane().getItems()).toHaveLength 1
|
||||
expect(atom.workspaceView.getActivePaneView().getItems()).toHaveLength 1
|
||||
atom.workspaceView.trigger('core:close')
|
||||
expect(atom.workspaceView.getActivePane().getItems()).toHaveLength 0
|
||||
expect(atom.workspaceView.getActivePaneView().getItems()).toHaveLength 0
|
||||
|
||||
describe "the scrollbar visibility class", ->
|
||||
it "has a class based on the style of the scrollbar", ->
|
||||
|
||||
@@ -157,6 +157,9 @@ class Atom extends Model
|
||||
# Still set NODE_PATH since tasks may need it.
|
||||
process.env.NODE_PATH = exportsPath
|
||||
|
||||
# Make react.js faster
|
||||
process.env.NODE_ENV ?= 'production'
|
||||
|
||||
@config = new Config({configDirPath, resourcePath})
|
||||
@keymaps = new KeymapManager({configDirPath, resourcePath})
|
||||
@keymap = @keymaps # Deprecated
|
||||
|
||||
@@ -140,7 +140,10 @@ class AtomApplication
|
||||
@on 'application:open-file', -> @promptForPath(type: 'file')
|
||||
@on 'application:open-folder', -> @promptForPath(type: 'folder')
|
||||
@on 'application:open-dev', -> @promptForPath(devMode: true)
|
||||
@on 'application:inspect', ({x,y}) -> @focusedWindow().browserWindow.inspectElement(x, y)
|
||||
@on 'application:inspect', ({x,y, atomWindow}) ->
|
||||
atomWindow ?= @focusedWindow()
|
||||
atomWindow?.browserWindow.inspectElement(x, y)
|
||||
|
||||
@on 'application:open-documentation', -> shell.openExternal('https://atom.io/docs/latest/?app')
|
||||
@on 'application:install-update', -> @autoUpdateManager.install()
|
||||
@on 'application:check-for-update', => @autoUpdateManager.check()
|
||||
@@ -228,6 +231,18 @@ class AtomApplication
|
||||
else
|
||||
@sendCommandToFirstResponder(command)
|
||||
|
||||
# Public: Executes the given command on the given window.
|
||||
#
|
||||
# command - The string representing the command.
|
||||
# atomWindow - The {AtomWindow} to send the command to.
|
||||
# args - The optional arguments to pass along.
|
||||
sendCommandToWindow: (command, atomWindow, args...) ->
|
||||
unless @emit(command, args...)
|
||||
if atomWindow?
|
||||
atomWindow.sendCommand(command, args...)
|
||||
else
|
||||
@sendCommandToFirstResponder(command)
|
||||
|
||||
# Translates the command into OS X action and sends it to application's first
|
||||
# responder.
|
||||
sendCommandToFirstResponder: (command) ->
|
||||
@@ -296,6 +311,7 @@ class AtomApplication
|
||||
if existingWindow
|
||||
openedWindow = existingWindow
|
||||
openedWindow.openPath(pathToOpen, initialLine)
|
||||
openedWindow.restore()
|
||||
else
|
||||
if devMode
|
||||
try
|
||||
|
||||
@@ -107,7 +107,7 @@ class AtomWindow
|
||||
when 1 then @browserWindow.restart()
|
||||
|
||||
@browserWindow.on 'context-menu', (menuTemplate) =>
|
||||
new ContextMenu(menuTemplate, @browserWindow)
|
||||
new ContextMenu(menuTemplate, this)
|
||||
|
||||
if @isSpec
|
||||
# Spec window's web view should always have focus
|
||||
@@ -151,6 +151,8 @@ class AtomWindow
|
||||
|
||||
maximize: -> @browserWindow.maximize()
|
||||
|
||||
restore: -> @browserWindow.restore()
|
||||
|
||||
handlesAtomCommands: ->
|
||||
not @isSpecWindow() and @isWebViewFocused()
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@ Menu = require 'menu'
|
||||
|
||||
module.exports =
|
||||
class ContextMenu
|
||||
constructor: (template, browserWindow) ->
|
||||
constructor: (template, @atomWindow) ->
|
||||
template = @createClickHandlers(template)
|
||||
menu = Menu.buildFromTemplate(template)
|
||||
menu.popup(browserWindow)
|
||||
menu.popup(@atomWindow.browserWindow)
|
||||
|
||||
# It's necessary to build the event handlers in this process, otherwise
|
||||
# closures are drug across processes and failed to be garbage collected
|
||||
@@ -13,7 +13,10 @@ class ContextMenu
|
||||
createClickHandlers: (template) ->
|
||||
for item in template
|
||||
if item.command
|
||||
(item.commandOptions ?= {}).contextCommand = true
|
||||
item.click = do (item) ->
|
||||
=> global.atomApplication.sendCommand(item.command, item.commandOptions)
|
||||
item.commandOptions ?= {}
|
||||
item.commandOptions.contextCommand = true
|
||||
item.commandOptions.atomWindow = @atomWindow
|
||||
do (item) =>
|
||||
item.click = =>
|
||||
global.atomApplication.sendCommandToWindow(item.command, @atomWindow, item.commandOptions)
|
||||
item
|
||||
|
||||
@@ -1,33 +1,29 @@
|
||||
crypto = require 'crypto'
|
||||
fs = require 'fs'
|
||||
path = require 'path'
|
||||
os = require 'os'
|
||||
|
||||
CoffeeScript = require 'coffee-script'
|
||||
CSON = require 'season'
|
||||
mkdir = require('mkdirp').sync
|
||||
fs = require 'fs-plus'
|
||||
|
||||
tmpDir = if process.platform is 'win32' then os.tmpdir() else '/tmp'
|
||||
cacheDir = path.join(tmpDir, 'atom-compile-cache')
|
||||
cacheDir = path.join(fs.absolute('~/.atom'), 'compile-cache')
|
||||
coffeeCacheDir = path.join(cacheDir, 'coffee')
|
||||
CSON.setCacheDir(path.join(cacheDir, 'cson'))
|
||||
|
||||
getCachePath = (coffee) ->
|
||||
digest = crypto.createHash('sha1').update(coffee, 'utf8').digest('hex')
|
||||
path.join(coffeeCacheDir, "#{digest}.coffee")
|
||||
path.join(coffeeCacheDir, "#{digest}.js")
|
||||
|
||||
getCachedJavaScript = (cachePath) ->
|
||||
if stat = fs.statSyncNoException(cachePath)
|
||||
if fs.isFileSync(cachePath)
|
||||
try
|
||||
fs.readFileSync(cachePath, 'utf8') if stat.isFile()
|
||||
fs.readFileSync(cachePath, 'utf8')
|
||||
|
||||
compileCoffeeScript = (coffee, filePath, cachePath) ->
|
||||
{js,v3SourceMap} = CoffeeScript.compile(coffee, filename: filePath, sourceMap: true)
|
||||
{js, v3SourceMap} = CoffeeScript.compile(coffee, filename: filePath, sourceMap: true)
|
||||
# Include source map in the web page environment.
|
||||
if btoa? and JSON? and unescape? and encodeURIComponent?
|
||||
js = "#{js}\n//# sourceMappingURL=data:application/json;base64,#{btoa unescape encodeURIComponent v3SourceMap}\n//# sourceURL=#{filePath}"
|
||||
try
|
||||
mkdir(path.dirname(cachePath))
|
||||
fs.writeFileSync(cachePath, js)
|
||||
js
|
||||
|
||||
|
||||
+40
-19
@@ -18,9 +18,9 @@ pathWatcher = require 'pathwatcher'
|
||||
# ## Example
|
||||
#
|
||||
# ```coffeescript
|
||||
# atom.config.set('myplugin.key', 'value')
|
||||
# atom.config.observe 'myplugin.key', ->
|
||||
# console.log 'My configuration changed:', atom.config.get('myplugin.key')
|
||||
# atom.config.set('my-package.key', 'value')
|
||||
# atom.config.observe 'my-package.key', ->
|
||||
# console.log 'My configuration changed:', atom.config.get('my-package.key')
|
||||
# ```
|
||||
module.exports =
|
||||
class Config
|
||||
@@ -88,7 +88,7 @@ class Config
|
||||
_.extend hash, defaults
|
||||
@emit 'updated'
|
||||
|
||||
# Public: Get the path to the config file being used.
|
||||
# Public: Get the {String} path to the config file being used.
|
||||
getUserConfigPath: ->
|
||||
@configFilePath
|
||||
|
||||
@@ -98,10 +98,10 @@ class Config
|
||||
|
||||
# Public: Retrieves the setting for the given key.
|
||||
#
|
||||
# keyPath - The {String} name of the key to retrieve
|
||||
# keyPath - The {String} name of the key to retrieve.
|
||||
#
|
||||
# Returns the value from Atom's default settings, the user's configuration file,
|
||||
# or `null` if the key doesn't exist in either.
|
||||
# Returns the value from Atom's default settings, the user's configuration
|
||||
# file, or `null` if the key doesn't exist in either.
|
||||
get: (keyPath) ->
|
||||
value = _.valueForKeyPath(@settings, keyPath) ? _.valueForKeyPath(@defaultSettings, keyPath)
|
||||
_.deepClone(value)
|
||||
@@ -110,8 +110,8 @@ class Config
|
||||
#
|
||||
# keyPath - The {String} name of the key to retrieve
|
||||
#
|
||||
# Returns the value from Atom's default settings, the user's configuration file,
|
||||
# or `NaN` if the key doesn't exist in either.
|
||||
# Returns the value from Atom's default settings, the user's configuration
|
||||
# file, or `NaN` if the key doesn't exist in either.
|
||||
getInt: (keyPath) ->
|
||||
parseInt(@get(keyPath))
|
||||
|
||||
@@ -121,8 +121,8 @@ class Config
|
||||
# defaultValue - The integer {Number} to fall back to if the value isn't
|
||||
# positive, defaults to 0.
|
||||
#
|
||||
# Returns the value from Atom's default settings, the user's configuration file,
|
||||
# or `defaultValue` if the key value isn't greater than zero.
|
||||
# Returns the value from Atom's default settings, the user's configuration
|
||||
# file, or `defaultValue` if the key value isn't greater than zero.
|
||||
getPositiveInt: (keyPath, defaultValue=0) ->
|
||||
Math.max(@getInt(keyPath), 0) or defaultValue
|
||||
|
||||
@@ -130,13 +130,14 @@ class Config
|
||||
#
|
||||
# This value is stored in Atom's internal configuration file.
|
||||
#
|
||||
# keyPath - The {String} name of the key
|
||||
# value - The value of the setting
|
||||
# keyPath - The {String} name of the key.
|
||||
# value - The value of the setting.
|
||||
#
|
||||
# Returns the `value`.
|
||||
set: (keyPath, value) ->
|
||||
if @get(keyPath) != value
|
||||
value = undefined if _.valueForKeyPath(@defaultSettings, keyPath) == value
|
||||
if @get(keyPath) isnt value
|
||||
defaultValue = _.valueForKeyPath(@defaultSettings, keyPath)
|
||||
value = undefined if _.isEqual(defaultValue, value)
|
||||
_.setValueForKeyPath(@settings, keyPath, value)
|
||||
@update()
|
||||
value
|
||||
@@ -146,6 +147,8 @@ class Config
|
||||
# The new value will be `true` if the value is currently falsy and will be
|
||||
# `false` if the value is currently truthy.
|
||||
#
|
||||
# keyPath - The {String} name of the key.
|
||||
#
|
||||
# Returns the new value.
|
||||
toggle: (keyPath) ->
|
||||
@set(keyPath, !@get(keyPath))
|
||||
@@ -158,12 +161,30 @@ class Config
|
||||
restoreDefault: (keyPath) ->
|
||||
@set(keyPath, _.valueForKeyPath(@defaultSettings, keyPath))
|
||||
|
||||
# Public: Get the default value of the key path.
|
||||
#
|
||||
# keyPath - The {String} name of the key.
|
||||
#
|
||||
# Returns the default value.
|
||||
getDefault: (keyPath) ->
|
||||
defaultValue = _.valueForKeyPath(@defaultSettings, keyPath)
|
||||
_.deepClone(defaultValue)
|
||||
|
||||
# Public: Is the key path value its default value?
|
||||
#
|
||||
# keyPath - The {String} name of the key.
|
||||
#
|
||||
# Returns a {Boolean}, `true` if the current value is the default, `false`
|
||||
# otherwise.
|
||||
isDefault: (keyPath) ->
|
||||
not _.valueForKeyPath(@settings, keyPath)?
|
||||
|
||||
# Public: Push the value to the array at the key path.
|
||||
#
|
||||
# keyPath - The {String} key path.
|
||||
# value - The value to push to the array.
|
||||
#
|
||||
# Returns the new array length of the setting.
|
||||
# Returns the new array length {Number} of the setting.
|
||||
pushAtKeyPath: (keyPath, value) ->
|
||||
arrayValue = @get(keyPath) ? []
|
||||
result = arrayValue.push(value)
|
||||
@@ -175,7 +196,7 @@ class Config
|
||||
# keyPath - The {String} key path.
|
||||
# value - The value to shift onto the array.
|
||||
#
|
||||
# Returns the new array length of the setting.
|
||||
# Returns the new array length {Number} of the setting.
|
||||
unshiftAtKeyPath: (keyPath, value) ->
|
||||
arrayValue = @get(keyPath) ? []
|
||||
result = arrayValue.unshift(value)
|
||||
@@ -225,9 +246,9 @@ class Config
|
||||
callback(value) if options.callNow ? true
|
||||
subscription
|
||||
|
||||
# Public: Unobserve all callbacks on a given key
|
||||
# Public: Unobserve all callbacks on a given key.
|
||||
#
|
||||
# keyPath - The {String} name of the key to unobserve
|
||||
# keyPath - The {String} name of the key to unobserve.
|
||||
unobserve: (keyPath) ->
|
||||
@off("updated.#{keyPath.replace(/\./, '-')}")
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ class ContextMenuManager
|
||||
label: 'Inspect Element'
|
||||
command: 'application:inspect'
|
||||
executeAtBuild: (e) ->
|
||||
@.commandOptions = x: e.pageX, y: e.pageY
|
||||
@commandOptions = x: e.pageX, y: e.pageY
|
||||
]
|
||||
|
||||
# Public: Creates menu definitions from the object specified by the menu
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
React = require 'react'
|
||||
{div} = require 'reactionary'
|
||||
|
||||
module.exports =
|
||||
CursorComponent = React.createClass
|
||||
displayName: 'CursorComponent'
|
||||
|
||||
render: ->
|
||||
{top, left, height, width} = @props.cursor.getPixelRect()
|
||||
className = 'cursor'
|
||||
className += ' blink-off' if @props.blinkOff
|
||||
|
||||
div className: className, style: {top, left, height, width}
|
||||
+20
-6
@@ -1,5 +1,5 @@
|
||||
{Point, Range} = require 'text-buffer'
|
||||
{Emitter} = require 'emissary'
|
||||
{Model} = require 'theorist'
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
# Public: The `Cursor` class represents the little blinking line identifying
|
||||
@@ -8,9 +8,7 @@ _ = require 'underscore-plus'
|
||||
# Cursors belong to {Editor}s and have some metadata attached in the form
|
||||
# of a {Marker}.
|
||||
module.exports =
|
||||
class Cursor
|
||||
Emitter.includeInto(this)
|
||||
|
||||
class Cursor extends Model
|
||||
screenPosition: null
|
||||
bufferPosition: null
|
||||
goalColumn: null
|
||||
@@ -18,7 +16,8 @@ class Cursor
|
||||
needsAutoscroll: null
|
||||
|
||||
# Instantiated by an {Editor}
|
||||
constructor: ({@editor, @marker}) ->
|
||||
constructor: ({@editor, @marker, id}) ->
|
||||
@assignId(id)
|
||||
@updateVisibility()
|
||||
@marker.on 'changed', (e) =>
|
||||
@updateVisibility()
|
||||
@@ -27,7 +26,12 @@ class Cursor
|
||||
{textChanged} = e
|
||||
return if oldHeadScreenPosition.isEqual(newHeadScreenPosition)
|
||||
|
||||
# Supports old editor view
|
||||
@needsAutoscroll ?= @isLastCursor() and !textChanged
|
||||
|
||||
# Supports react editor view
|
||||
@autoscroll() if @needsAutoscroll and @editor.manageScrollPosition
|
||||
|
||||
@goalColumn = null
|
||||
|
||||
movedEvent =
|
||||
@@ -38,7 +42,7 @@ class Cursor
|
||||
textChanged: textChanged
|
||||
|
||||
@emit 'moved', movedEvent
|
||||
@editor.emit 'cursor-moved', movedEvent
|
||||
@editor.cursorMoved(movedEvent)
|
||||
@marker.on 'destroyed', =>
|
||||
@destroyed = true
|
||||
@editor.removeCursor(this)
|
||||
@@ -54,6 +58,9 @@ class Cursor
|
||||
unless fn()
|
||||
@emit 'autoscrolled' if @needsAutoscroll
|
||||
|
||||
getPixelRect: ->
|
||||
@editor.pixelRectForScreenRange(@getScreenRange())
|
||||
|
||||
# Public: Moves a cursor to a given screen position.
|
||||
#
|
||||
# screenPosition - An {Array} of two numbers: the screen row, and the screen
|
||||
@@ -69,6 +76,10 @@ class Cursor
|
||||
getScreenPosition: ->
|
||||
@marker.getHeadScreenPosition()
|
||||
|
||||
getScreenRange: ->
|
||||
{row, column} = @getScreenPosition()
|
||||
new Range(new Point(row, column), new Point(row, column + 1))
|
||||
|
||||
# Public: Moves a cursor to a given buffer position.
|
||||
#
|
||||
# bufferPosition - An {Array} of two numbers: the buffer row, and the buffer
|
||||
@@ -84,6 +95,9 @@ class Cursor
|
||||
getBufferPosition: ->
|
||||
@marker.getHeadBufferPosition()
|
||||
|
||||
autoscroll: ->
|
||||
@editor.scrollToScreenRange(@getScreenRange())
|
||||
|
||||
# Public: If the marker range is empty, the cursor is marked as being visible.
|
||||
updateVisibility: ->
|
||||
@setVisible(@marker.getBufferRange().isEmpty())
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
React = require 'react'
|
||||
{div} = require 'reactionary'
|
||||
{debounce} = require 'underscore-plus'
|
||||
SubscriberMixin = require './subscriber-mixin'
|
||||
CursorComponent = require './cursor-component'
|
||||
|
||||
|
||||
module.exports =
|
||||
CursorsComponent = React.createClass
|
||||
displayName: 'CursorsComponent'
|
||||
mixins: [SubscriberMixin]
|
||||
|
||||
cursorBlinkIntervalHandle: null
|
||||
|
||||
render: ->
|
||||
{editor} = @props
|
||||
blinkOff = @state.blinkCursorsOff
|
||||
|
||||
div className: 'cursors',
|
||||
if @isMounted()
|
||||
for selection in editor.getSelections()
|
||||
if selection.isEmpty() and editor.selectionIntersectsVisibleRowRange(selection)
|
||||
{cursor} = selection
|
||||
CursorComponent({key: cursor.id, cursor, blinkOff})
|
||||
|
||||
getInitialState: ->
|
||||
blinkCursorsOff: false
|
||||
|
||||
componentDidMount: ->
|
||||
{editor} = @props
|
||||
@startBlinkingCursors()
|
||||
|
||||
componentWillUnmount: ->
|
||||
clearInterval(@cursorBlinkIntervalHandle)
|
||||
|
||||
componentWillUpdate: ({cursorsMoved}) ->
|
||||
@pauseCursorBlinking() if cursorsMoved
|
||||
|
||||
startBlinkingCursors: ->
|
||||
@cursorBlinkIntervalHandle = setInterval(@toggleCursorBlink, @props.cursorBlinkPeriod / 2)
|
||||
|
||||
startBlinkingCursorsAfterDelay: null # Created lazily
|
||||
|
||||
toggleCursorBlink: -> @setState(blinkCursorsOff: not @state.blinkCursorsOff)
|
||||
|
||||
pauseCursorBlinking: ->
|
||||
@state.blinkCursorsOff = false
|
||||
clearInterval(@cursorBlinkIntervalHandle)
|
||||
@startBlinkingCursorsAfterDelay ?= debounce(@startBlinkingCursors, @props.cursorBlinkResumeDelay)
|
||||
@startBlinkingCursorsAfterDelay()
|
||||
@@ -0,0 +1,15 @@
|
||||
module.exports =
|
||||
CustomEventMixin =
|
||||
componentWillMount: ->
|
||||
@customEventListeners = {}
|
||||
|
||||
componentWillUnmount: ->
|
||||
for name, listeners in @customEventListeners
|
||||
for listener in listeners
|
||||
@getDOMNode().removeEventListener(name, listener)
|
||||
|
||||
addCustomEventListeners: (customEventListeners) ->
|
||||
for name, listener of customEventListeners
|
||||
@customEventListeners[name] ?= []
|
||||
@customEventListeners[name].push(listener)
|
||||
@getDOMNode().addEventListener(name, listener)
|
||||
@@ -54,6 +54,9 @@ class DisplayBufferMarker
|
||||
setBufferRange: (bufferRange, options) ->
|
||||
@bufferMarker.setRange(bufferRange, options)
|
||||
|
||||
getPixelRange: ->
|
||||
@displayBuffer.pixelRangeForScreenRange(@getScreenRange(), false)
|
||||
|
||||
# Retrieves the screen position of the marker's head.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
@@ -140,10 +143,10 @@ class DisplayBufferMarker
|
||||
@bufferMarker.isDestroyed()
|
||||
|
||||
getAttributes: ->
|
||||
@bufferMarker.getAttributes()
|
||||
@bufferMarker.getProperties()
|
||||
|
||||
setAttributes: (attributes) ->
|
||||
@bufferMarker.setAttributes(attributes)
|
||||
@bufferMarker.setProperties(attributes)
|
||||
|
||||
matchesAttributes: (attributes) ->
|
||||
attributes = @displayBuffer.translateToBufferMarkerParams(attributes)
|
||||
|
||||
+222
-17
@@ -20,14 +20,25 @@ class DisplayBuffer extends Model
|
||||
Serializable.includeInto(this)
|
||||
|
||||
@properties
|
||||
manageScrollPosition: false
|
||||
softWrap: null
|
||||
editorWidthInChars: null
|
||||
lineHeight: null
|
||||
defaultCharWidth: null
|
||||
height: null
|
||||
width: null
|
||||
scrollTop: 0
|
||||
scrollLeft: 0
|
||||
|
||||
verticalScrollMargin: 2
|
||||
horizontalScrollMargin: 6
|
||||
|
||||
constructor: ({tabLength, @editorWidthInChars, @tokenizedBuffer, buffer}={}) ->
|
||||
super
|
||||
@softWrap ?= atom.config.get('editor.softWrap') ? false
|
||||
@tokenizedBuffer ?= new TokenizedBuffer({tabLength, buffer})
|
||||
@buffer = @tokenizedBuffer.buffer
|
||||
@charWidthsByScope = {}
|
||||
@markers = {}
|
||||
@foldsByMarkerId = {}
|
||||
@updateAllScreenLines()
|
||||
@@ -51,6 +62,8 @@ class DisplayBuffer extends Model
|
||||
id: @id
|
||||
softWrap: @softWrap
|
||||
editorWidthInChars: @editorWidthInChars
|
||||
scrollTop: @scrollTop
|
||||
scrollLeft: @scrollLeft
|
||||
tokenizedBuffer: @tokenizedBuffer.serialize()
|
||||
|
||||
deserializeParams: (params) ->
|
||||
@@ -59,6 +72,9 @@ class DisplayBuffer extends Model
|
||||
|
||||
copy: ->
|
||||
newDisplayBuffer = new DisplayBuffer({@buffer, tabLength: @getTabLength()})
|
||||
newDisplayBuffer.setScrollTop(@getScrollTop())
|
||||
newDisplayBuffer.setScrollLeft(@getScrollLeft())
|
||||
|
||||
for marker in @findMarkers(displayBufferId: @id)
|
||||
marker.copy(displayBufferId: newDisplayBuffer.id)
|
||||
newDisplayBuffer
|
||||
@@ -89,6 +105,151 @@ class DisplayBuffer extends Model
|
||||
# visible - A {Boolean} indicating of the tokenized buffer is shown
|
||||
setVisible: (visible) -> @tokenizedBuffer.setVisible(visible)
|
||||
|
||||
getVerticalScrollMargin: -> @verticalScrollMargin
|
||||
setVerticalScrollMargin: (@verticalScrollMargin) -> @verticalScrollMargin
|
||||
|
||||
getHorizontalScrollMargin: -> @horizontalScrollMargin
|
||||
setHorizontalScrollMargin: (@horizontalScrollMargin) -> @horizontalScrollMargin
|
||||
|
||||
getHeight: -> @height ? @getScrollHeight()
|
||||
setHeight: (@height) -> @height
|
||||
|
||||
getWidth: -> @width ? @getScrollWidth()
|
||||
setWidth: (newWidth) ->
|
||||
oldWidth = @width
|
||||
@width = newWidth
|
||||
@updateWrappedScreenLines() if newWidth isnt oldWidth and @softWrap
|
||||
@width
|
||||
|
||||
getScrollTop: -> @scrollTop
|
||||
setScrollTop: (scrollTop) ->
|
||||
if @manageScrollPosition
|
||||
@scrollTop = Math.max(0, Math.min(@getScrollHeight() - @getHeight(), scrollTop))
|
||||
else
|
||||
@scrollTop = scrollTop
|
||||
|
||||
getScrollBottom: -> @scrollTop + @height
|
||||
setScrollBottom: (scrollBottom) ->
|
||||
@setScrollTop(scrollBottom - @height)
|
||||
@getScrollBottom()
|
||||
|
||||
getScrollLeft: -> @scrollLeft
|
||||
setScrollLeft: (scrollLeft) ->
|
||||
if @manageScrollPosition
|
||||
@scrollLeft = Math.max(0, Math.min(@getScrollWidth() - @getWidth(), scrollLeft))
|
||||
else
|
||||
@scrollLeft = scrollLeft
|
||||
|
||||
getScrollRight: -> @scrollLeft + @width
|
||||
setScrollRight: (scrollRight) ->
|
||||
@setScrollLeft(scrollRight - @width)
|
||||
@getScrollRight()
|
||||
|
||||
getLineHeight: -> @lineHeight
|
||||
setLineHeight: (@lineHeight) -> @lineHeight
|
||||
|
||||
getDefaultCharWidth: -> @defaultCharWidth
|
||||
setDefaultCharWidth: (@defaultCharWidth) -> @defaultCharWidth
|
||||
|
||||
getScopedCharWidth: (scopeNames, char) ->
|
||||
@getScopedCharWidths(scopeNames)[char]
|
||||
|
||||
getScopedCharWidths: (scopeNames) ->
|
||||
scope = @charWidthsByScope
|
||||
for scopeName in scopeNames
|
||||
scope[scopeName] ?= {}
|
||||
scope = scope[scopeName]
|
||||
scope.charWidths ?= {}
|
||||
scope.charWidths
|
||||
|
||||
setScopedCharWidth: (scopeNames, char, width) ->
|
||||
@getScopedCharWidths(scopeNames)[char] = width
|
||||
|
||||
setScopedCharWidths: (scopeNames, charWidths) ->
|
||||
_.extend(@getScopedCharWidths(scopeNames), charWidths)
|
||||
|
||||
clearScopedCharWidths: ->
|
||||
@charWidthsByScope = {}
|
||||
|
||||
getScrollHeight: ->
|
||||
unless @getLineHeight() > 0
|
||||
throw new Error("You must assign lineHeight before calling ::getScrollHeight()")
|
||||
|
||||
@getLineCount() * @getLineHeight()
|
||||
|
||||
getScrollWidth: ->
|
||||
@getMaxLineLength() * @getDefaultCharWidth()
|
||||
|
||||
getVisibleRowRange: ->
|
||||
unless @getLineHeight() > 0
|
||||
throw new Error("You must assign a non-zero lineHeight before calling ::getVisibleRowRange()")
|
||||
|
||||
heightInLines = Math.ceil(@getHeight() / @getLineHeight()) + 1
|
||||
startRow = Math.floor(@getScrollTop() / @getLineHeight())
|
||||
endRow = Math.min(@getLineCount(), Math.ceil(startRow + heightInLines))
|
||||
[startRow, endRow]
|
||||
|
||||
intersectsVisibleRowRange: (startRow, endRow) ->
|
||||
[visibleStart, visibleEnd] = @getVisibleRowRange()
|
||||
not (endRow <= visibleStart or visibleEnd <= startRow)
|
||||
|
||||
selectionIntersectsVisibleRowRange: (selection) ->
|
||||
{start, end} = selection.getScreenRange()
|
||||
@intersectsVisibleRowRange(start.row, end.row + 1)
|
||||
|
||||
scrollToScreenRange: (screenRange) ->
|
||||
verticalScrollMarginInPixels = @getVerticalScrollMargin() * @getLineHeight()
|
||||
horizontalScrollMarginInPixels = @getHorizontalScrollMargin() * @getDefaultCharWidth()
|
||||
|
||||
{top, left, height, width} = @pixelRectForScreenRange(screenRange)
|
||||
bottom = top + height
|
||||
right = left + width
|
||||
desiredScrollTop = top - verticalScrollMarginInPixels
|
||||
desiredScrollBottom = bottom + verticalScrollMarginInPixels
|
||||
desiredScrollLeft = left - horizontalScrollMarginInPixels
|
||||
desiredScrollRight = right + horizontalScrollMarginInPixels
|
||||
|
||||
if desiredScrollTop < @getScrollTop()
|
||||
@setScrollTop(desiredScrollTop)
|
||||
else if desiredScrollBottom > @getScrollBottom()
|
||||
@setScrollBottom(desiredScrollBottom)
|
||||
|
||||
if desiredScrollLeft < @getScrollLeft()
|
||||
@setScrollLeft(desiredScrollLeft)
|
||||
else if desiredScrollRight > @getScrollRight()
|
||||
@setScrollRight(desiredScrollRight)
|
||||
|
||||
scrollToScreenPosition: (screenPosition) ->
|
||||
@scrollToScreenRange(new Range(screenPosition, screenPosition))
|
||||
|
||||
scrollToBufferPosition: (bufferPosition) ->
|
||||
@scrollToScreenPosition(@screenPositionForBufferPosition(bufferPosition))
|
||||
|
||||
pixelRectForScreenRange: (screenRange) ->
|
||||
if screenRange.end.row > screenRange.start.row
|
||||
top = @pixelPositionForScreenPosition(screenRange.start).top
|
||||
left = 0
|
||||
height = (screenRange.end.row - screenRange.start.row + 1) * @getLineHeight()
|
||||
width = @getScrollWidth()
|
||||
else
|
||||
{top, left} = @pixelPositionForScreenPosition(screenRange.start)
|
||||
height = @getLineHeight()
|
||||
width = @pixelPositionForScreenPosition(screenRange.end).left - left
|
||||
|
||||
{top, left, width, height}
|
||||
|
||||
# Retrieves the current tab length.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getTabLength: ->
|
||||
@tokenizedBuffer.getTabLength()
|
||||
|
||||
# Specifies the tab length.
|
||||
#
|
||||
# tabLength - A {Number} that defines the new tab length.
|
||||
setTabLength: (tabLength) ->
|
||||
@tokenizedBuffer.setTabLength(tabLength)
|
||||
|
||||
# Deprecated: Use the softWrap property directly
|
||||
setSoftWrap: (@softWrap) -> @softWrap
|
||||
|
||||
@@ -105,12 +266,19 @@ class DisplayBuffer extends Model
|
||||
if editorWidthInChars isnt previousWidthInChars and @softWrap
|
||||
@updateWrappedScreenLines()
|
||||
|
||||
getSoftWrapColumn: ->
|
||||
if atom.config.get('editor.softWrapAtPreferredLineLength')
|
||||
Math.min(@editorWidthInChars, atom.config.getPositiveInt('editor.preferredLineLength', @editorWidthInChars))
|
||||
getEditorWidthInChars: ->
|
||||
width = @getWidth()
|
||||
if width? and @defaultCharWidth > 0
|
||||
Math.floor(width / @defaultCharWidth)
|
||||
else
|
||||
@editorWidthInChars
|
||||
|
||||
getSoftWrapColumn: ->
|
||||
if atom.config.get('editor.softWrapAtPreferredLineLength')
|
||||
Math.min(@getEditorWidthInChars(), atom.config.getPositiveInt('editor.preferredLineLength', @getEditorWidthInChars()))
|
||||
else
|
||||
@getEditorWidthInChars()
|
||||
|
||||
# Gets the screen line for the given screen row.
|
||||
#
|
||||
# screenRow - A {Number} indicating the screen row.
|
||||
@@ -134,6 +302,9 @@ class DisplayBuffer extends Model
|
||||
getLines: ->
|
||||
new Array(@screenLines...)
|
||||
|
||||
indentLevelForLine: (line) ->
|
||||
@tokenizedBuffer.indentLevelForLine(line)
|
||||
|
||||
# Given starting and ending screen rows, this returns an array of the
|
||||
# buffer rows corresponding to every screen row in the range
|
||||
#
|
||||
@@ -273,6 +444,52 @@ class DisplayBuffer extends Model
|
||||
end = @bufferPositionForScreenPosition(screenRange.end)
|
||||
new Range(start, end)
|
||||
|
||||
pixelRangeForScreenRange: (screenRange, clip=true) ->
|
||||
{start, end} = Range.fromObject(screenRange)
|
||||
{start: @pixelPositionForScreenPosition(start, clip), end: @pixelPositionForScreenPosition(end, clip)}
|
||||
|
||||
pixelPositionForScreenPosition: (screenPosition, clip=true) ->
|
||||
screenPosition = Point.fromObject(screenPosition)
|
||||
screenPosition = @clipScreenPosition(screenPosition) if clip
|
||||
|
||||
targetRow = screenPosition.row
|
||||
targetColumn = screenPosition.column
|
||||
defaultCharWidth = @defaultCharWidth
|
||||
|
||||
top = targetRow * @lineHeight
|
||||
left = 0
|
||||
column = 0
|
||||
for token in @lineForRow(targetRow).tokens
|
||||
charWidths = @getScopedCharWidths(token.scopes)
|
||||
for char in token.value
|
||||
return {top, left} if column is targetColumn
|
||||
left += charWidths[char] ? defaultCharWidth
|
||||
column++
|
||||
{top, left}
|
||||
|
||||
screenPositionForPixelPosition: (pixelPosition) ->
|
||||
targetTop = pixelPosition.top
|
||||
targetLeft = pixelPosition.left
|
||||
defaultCharWidth = @defaultCharWidth
|
||||
row = Math.floor(targetTop / @getLineHeight())
|
||||
row = Math.min(row, @getLastRow())
|
||||
row = Math.max(0, row)
|
||||
|
||||
left = 0
|
||||
column = 0
|
||||
for token in @lineForRow(row).tokens
|
||||
charWidths = @getScopedCharWidths(token.scopes)
|
||||
for char in token.value
|
||||
charWidth = charWidths[char] ? defaultCharWidth
|
||||
break if targetLeft <= left + (charWidth / 2)
|
||||
left += charWidth
|
||||
column++
|
||||
|
||||
new Point(row, column)
|
||||
|
||||
pixelPositionForBufferPosition: (bufferPosition) ->
|
||||
@pixelPositionForScreenPosition(@screenPositionForBufferPosition(bufferPosition))
|
||||
|
||||
# Gets the number of screen lines.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
@@ -358,18 +575,6 @@ class DisplayBuffer extends Model
|
||||
tokenForBufferPosition: (bufferPosition) ->
|
||||
@tokenizedBuffer.tokenForPosition(bufferPosition)
|
||||
|
||||
# Retrieves the current tab length.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getTabLength: ->
|
||||
@tokenizedBuffer.getTabLength()
|
||||
|
||||
# Specifies the tab length.
|
||||
#
|
||||
# tabLength - A {Number} that defines the new tab length.
|
||||
setTabLength: (tabLength) ->
|
||||
@tokenizedBuffer.setTabLength(tabLength)
|
||||
|
||||
# Get the grammar for this buffer.
|
||||
#
|
||||
# Returns the current {Grammar} or the {NullGrammar}.
|
||||
@@ -493,8 +698,8 @@ class DisplayBuffer extends Model
|
||||
# options - Options to pass to the {Marker} constructor
|
||||
#
|
||||
# Returns a {Number} representing the new marker's ID.
|
||||
markBufferRange: (args...) ->
|
||||
@getMarker(@buffer.markRange(args...).id)
|
||||
markBufferRange: (range, options) ->
|
||||
@getMarker(@buffer.markRange(range, options).id)
|
||||
|
||||
# Public: Constructs a new marker at the given screen position.
|
||||
#
|
||||
|
||||
@@ -0,0 +1,338 @@
|
||||
React = require 'react'
|
||||
{div, span} = require 'reactionary'
|
||||
{debounce} = require 'underscore-plus'
|
||||
|
||||
GutterComponent = require './gutter-component'
|
||||
EditorScrollViewComponent = require './editor-scroll-view-component'
|
||||
ScrollbarComponent = require './scrollbar-component'
|
||||
SubscriberMixin = require './subscriber-mixin'
|
||||
|
||||
module.exports =
|
||||
EditorComponent = React.createClass
|
||||
displayName: 'EditorComponent'
|
||||
mixins: [SubscriberMixin]
|
||||
|
||||
pendingScrollTop: null
|
||||
pendingScrollLeft: null
|
||||
selectOnMouseMove: false
|
||||
batchingUpdates: false
|
||||
updateRequested: false
|
||||
cursorsMoved: false
|
||||
preservedRowRange: null
|
||||
scrollingVertically: false
|
||||
|
||||
render: ->
|
||||
{focused, fontSize, lineHeight, fontFamily, showIndentGuide} = @state
|
||||
{editor, cursorBlinkPeriod, cursorBlinkResumeDelay} = @props
|
||||
if @isMounted()
|
||||
renderedRowRange = @getRenderedRowRange()
|
||||
scrollHeight = editor.getScrollHeight()
|
||||
scrollWidth = editor.getScrollWidth()
|
||||
scrollTop = editor.getScrollTop()
|
||||
scrollLeft = editor.getScrollLeft()
|
||||
lineHeightInPixels = editor.getLineHeight()
|
||||
|
||||
className = 'editor editor-colors react'
|
||||
className += ' is-focused' if focused
|
||||
|
||||
div className: className, style: {fontSize, lineHeight, fontFamily}, tabIndex: -1,
|
||||
GutterComponent {
|
||||
editor, renderedRowRange, scrollTop, scrollHeight,
|
||||
lineHeight: lineHeightInPixels, @pendingChanges
|
||||
}
|
||||
|
||||
EditorScrollViewComponent {
|
||||
ref: 'scrollView', editor, fontSize, fontFamily, showIndentGuide
|
||||
scrollHeight, scrollWidth, lineHeight: lineHeightInPixels,
|
||||
renderedRowRange, @pendingChanges, @scrollingVertically, @cursorsMoved,
|
||||
cursorBlinkPeriod, cursorBlinkResumeDelay, @onInputFocused, @onInputBlurred
|
||||
}
|
||||
|
||||
ScrollbarComponent
|
||||
ref: 'verticalScrollbar'
|
||||
className: 'vertical-scrollbar'
|
||||
orientation: 'vertical'
|
||||
onScroll: @onVerticalScroll
|
||||
scrollTop: scrollTop
|
||||
scrollHeight: scrollHeight
|
||||
|
||||
ScrollbarComponent
|
||||
ref: 'horizontalScrollbar'
|
||||
className: 'horizontal-scrollbar'
|
||||
orientation: 'horizontal'
|
||||
onScroll: @onHorizontalScroll
|
||||
scrollLeft: scrollLeft
|
||||
scrollWidth: scrollWidth
|
||||
|
||||
getRenderedRowRange: ->
|
||||
renderedRowRange = @props.editor.getVisibleRowRange()
|
||||
if @preservedRowRange?
|
||||
renderedRowRange[0] = Math.min(@preservedRowRange[0], renderedRowRange[0])
|
||||
renderedRowRange[1] = Math.max(@preservedRowRange[1], renderedRowRange[1])
|
||||
renderedRowRange
|
||||
|
||||
getInitialState: -> {}
|
||||
|
||||
getDefaultProps: ->
|
||||
cursorBlinkPeriod: 800
|
||||
cursorBlinkResumeDelay: 200
|
||||
|
||||
componentWillMount: ->
|
||||
@pendingChanges = []
|
||||
@props.editor.manageScrollPosition = true
|
||||
@observeConfig()
|
||||
|
||||
componentDidMount: ->
|
||||
@observeEditor()
|
||||
@listenForDOMEvents()
|
||||
@listenForCommands()
|
||||
@props.editor.setVisible(true)
|
||||
@requestUpdate()
|
||||
|
||||
componentWillUnmount: ->
|
||||
@unsubscribe()
|
||||
@getDOMNode().removeEventListener 'mousewheel', @onMouseWheel
|
||||
|
||||
componentWillUpdate: ->
|
||||
@props.parentView.trigger 'cursor:moved' if @cursorsMoved
|
||||
|
||||
componentDidUpdate: ->
|
||||
@pendingChanges.length = 0
|
||||
@cursorsMoved = false
|
||||
@props.parentView.trigger 'editor:display-updated'
|
||||
|
||||
observeEditor: ->
|
||||
{editor} = @props
|
||||
@subscribe editor, 'batched-updates-started', @onBatchedUpdatesStarted
|
||||
@subscribe editor, 'batched-updates-ended', @onBatchedUpdatesEnded
|
||||
@subscribe editor, 'screen-lines-changed', @onScreenLinesChanged
|
||||
@subscribe editor, 'cursors-moved', @onCursorsMoved
|
||||
@subscribe editor, 'selection-screen-range-changed', @requestUpdate
|
||||
@subscribe editor, 'selection-added', @onSelectionAdded
|
||||
@subscribe editor, 'selection-removed', @onSelectionAdded
|
||||
@subscribe editor.$scrollTop.changes, @onScrollTopChanged
|
||||
@subscribe editor.$scrollLeft.changes, @requestUpdate
|
||||
@subscribe editor.$height.changes, @requestUpdate
|
||||
@subscribe editor.$width.changes, @requestUpdate
|
||||
@subscribe editor.$defaultCharWidth.changes, @requestUpdate
|
||||
@subscribe editor.$lineHeight.changes, @requestUpdate
|
||||
|
||||
listenForDOMEvents: ->
|
||||
node = @getDOMNode()
|
||||
node.addEventListener 'mousewheel', @onMouseWheel
|
||||
node.addEventListener 'focus', @onFocus # For some reason, React's built in focus events seem to bubble
|
||||
|
||||
listenForCommands: ->
|
||||
{parentView, editor, mini} = @props
|
||||
|
||||
@addCommandListeners
|
||||
'core:move-left': => editor.moveCursorLeft()
|
||||
'core:move-right': => editor.moveCursorRight()
|
||||
'core:select-left': => editor.selectLeft()
|
||||
'core:select-right': => editor.selectRight()
|
||||
'core:select-all': => editor.selectAll()
|
||||
'core:backspace': => editor.backspace()
|
||||
'core:delete': => editor.delete()
|
||||
'core:undo': => editor.undo()
|
||||
'core:redo': => editor.redo()
|
||||
'core:cut': => editor.cutSelectedText()
|
||||
'core:copy': => editor.copySelectedText()
|
||||
'core:paste': => editor.pasteText()
|
||||
'editor:move-to-previous-word': => editor.moveCursorToPreviousWord()
|
||||
'editor:select-word': => editor.selectWord()
|
||||
'editor:consolidate-selections': @consolidateSelections
|
||||
'editor:backspace-to-beginning-of-word': => editor.backspaceToBeginningOfWord()
|
||||
'editor:backspace-to-beginning-of-line': => editor.backspaceToBeginningOfLine()
|
||||
'editor:delete-to-end-of-word': => editor.deleteToEndOfWord()
|
||||
'editor:delete-line': => editor.deleteLine()
|
||||
'editor:cut-to-end-of-line': => editor.cutToEndOfLine()
|
||||
'editor:move-to-beginning-of-screen-line': => editor.moveCursorToBeginningOfScreenLine()
|
||||
'editor:move-to-beginning-of-line': => editor.moveCursorToBeginningOfLine()
|
||||
'editor:move-to-end-of-screen-line': => editor.moveCursorToEndOfScreenLine()
|
||||
'editor:move-to-end-of-line': => editor.moveCursorToEndOfLine()
|
||||
'editor:move-to-first-character-of-line': => editor.moveCursorToFirstCharacterOfLine()
|
||||
'editor:move-to-beginning-of-word': => editor.moveCursorToBeginningOfWord()
|
||||
'editor:move-to-end-of-word': => editor.moveCursorToEndOfWord()
|
||||
'editor:move-to-beginning-of-next-word': => editor.moveCursorToBeginningOfNextWord()
|
||||
'editor:move-to-previous-word-boundary': => editor.moveCursorToPreviousWordBoundary()
|
||||
'editor:move-to-next-word-boundary': => editor.moveCursorToNextWordBoundary()
|
||||
'editor:select-to-end-of-line': => editor.selectToEndOfLine()
|
||||
'editor:select-to-beginning-of-line': => editor.selectToBeginningOfLine()
|
||||
'editor:select-to-end-of-word': => editor.selectToEndOfWord()
|
||||
'editor:select-to-beginning-of-word': => editor.selectToBeginningOfWord()
|
||||
'editor:select-to-beginning-of-next-word': => editor.selectToBeginningOfNextWord()
|
||||
'editor:select-to-next-word-boundary': => editor.selectToNextWordBoundary()
|
||||
'editor:select-to-previous-word-boundary': => editor.selectToPreviousWordBoundary()
|
||||
'editor:select-to-first-character-of-line': => editor.selectToFirstCharacterOfLine()
|
||||
'editor:select-line': => editor.selectLine()
|
||||
'editor:transpose': => editor.transpose()
|
||||
'editor:upper-case': => editor.upperCase()
|
||||
'editor:lower-case': => editor.lowerCase()
|
||||
|
||||
unless mini
|
||||
@addCommandListeners
|
||||
'core:move-up': => editor.moveCursorUp()
|
||||
'core:move-down': => editor.moveCursorDown()
|
||||
'core:move-to-top': => editor.moveCursorToTop()
|
||||
'core:move-to-bottom': => editor.moveCursorToBottom()
|
||||
'core:select-up': => editor.selectUp()
|
||||
'core:select-down': => editor.selectDown()
|
||||
'core:select-to-top': => editor.selectToTop()
|
||||
'core:select-to-bottom': => editor.selectToBottom()
|
||||
'editor:indent': => editor.indent()
|
||||
'editor:auto-indent': => editor.autoIndentSelectedRows()
|
||||
'editor:indent-selected-rows': => editor.indentSelectedRows()
|
||||
'editor:outdent-selected-rows': => editor.outdentSelectedRows()
|
||||
'editor:newline': => editor.insertNewline()
|
||||
'editor:newline-below': => editor.insertNewlineBelow()
|
||||
'editor:newline-above': => editor.insertNewlineAbove()
|
||||
'editor:add-selection-below': => editor.addSelectionBelow()
|
||||
'editor:add-selection-above': => editor.addSelectionAbove()
|
||||
'editor:split-selections-into-lines': => editor.splitSelectionsIntoLines()
|
||||
'editor:toggle-soft-tabs': => editor.toggleSoftTabs()
|
||||
'editor:toggle-soft-wrap': => editor.toggleSoftWrap()
|
||||
'editor:fold-all': => editor.foldAll()
|
||||
'editor:unfold-all': => editor.unfoldAll()
|
||||
'editor:fold-current-row': => editor.foldCurrentRow()
|
||||
'editor:unfold-current-row': => editor.unfoldCurrentRow()
|
||||
'editor:fold-selection': => neditor.foldSelectedLines()
|
||||
'editor:fold-at-indent-level-1': => editor.foldAllAtIndentLevel(0)
|
||||
'editor:fold-at-indent-level-2': => editor.foldAllAtIndentLevel(1)
|
||||
'editor:fold-at-indent-level-3': => editor.foldAllAtIndentLevel(2)
|
||||
'editor:fold-at-indent-level-4': => editor.foldAllAtIndentLevel(3)
|
||||
'editor:fold-at-indent-level-5': => editor.foldAllAtIndentLevel(4)
|
||||
'editor:fold-at-indent-level-6': => editor.foldAllAtIndentLevel(5)
|
||||
'editor:fold-at-indent-level-7': => editor.foldAllAtIndentLevel(6)
|
||||
'editor:fold-at-indent-level-8': => editor.foldAllAtIndentLevel(7)
|
||||
'editor:fold-at-indent-level-9': => editor.foldAllAtIndentLevel(8)
|
||||
'editor:toggle-line-comments': => editor.toggleLineCommentsInSelection()
|
||||
'editor:log-cursor-scope': => editor.logCursorScope()
|
||||
'editor:checkout-head-revision': => editor.checkoutHead()
|
||||
'editor:copy-path': => editor.copyPathToClipboard()
|
||||
'editor:move-line-up': => editor.moveLineUp()
|
||||
'editor:move-line-down': => editor.moveLineDown()
|
||||
'editor:duplicate-lines': => editor.duplicateLines()
|
||||
'editor:join-lines': => editor.joinLines()
|
||||
'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()
|
||||
|
||||
addCommandListeners: (listenersByCommandName) ->
|
||||
{parentView} = @props
|
||||
|
||||
for command, listener of listenersByCommandName
|
||||
parentView.command command, listener
|
||||
|
||||
observeConfig: ->
|
||||
@subscribe atom.config.observe 'editor.fontFamily', @setFontFamily
|
||||
@subscribe atom.config.observe 'editor.fontSize', @setFontSize
|
||||
@subscribe atom.config.observe 'editor.showIndentGuide', @setShowIndentGuide
|
||||
|
||||
setFontSize: (fontSize) ->
|
||||
@setState({fontSize})
|
||||
|
||||
setLineHeight: (lineHeight) ->
|
||||
@setState({lineHeight})
|
||||
|
||||
setFontFamily: (fontFamily) ->
|
||||
@setState({fontFamily})
|
||||
|
||||
setShowIndentGuide: (showIndentGuide) ->
|
||||
@setState({showIndentGuide})
|
||||
|
||||
onFocus: ->
|
||||
@refs.scrollView.focus()
|
||||
|
||||
onInputFocused: ->
|
||||
@setState(focused: true)
|
||||
|
||||
onInputBlurred: ->
|
||||
@setState(focused: false)
|
||||
|
||||
onVerticalScroll: (scrollTop) ->
|
||||
{editor} = @props
|
||||
|
||||
return if scrollTop is editor.getScrollTop()
|
||||
|
||||
animationFramePending = @pendingScrollTop?
|
||||
@pendingScrollTop = scrollTop
|
||||
unless animationFramePending
|
||||
requestAnimationFrame =>
|
||||
@props.editor.setScrollTop(@pendingScrollTop)
|
||||
@pendingScrollTop = null
|
||||
|
||||
onHorizontalScroll: (scrollLeft) ->
|
||||
{editor} = @props
|
||||
|
||||
return if scrollLeft is editor.getScrollLeft()
|
||||
|
||||
animationFramePending = @pendingScrollLeft?
|
||||
@pendingScrollLeft = scrollLeft
|
||||
unless animationFramePending
|
||||
requestAnimationFrame =>
|
||||
@props.editor.setScrollLeft(@pendingScrollLeft)
|
||||
@pendingScrollLeft = null
|
||||
|
||||
onMouseWheel: (event) ->
|
||||
# Only scroll in one direction at a time
|
||||
{wheelDeltaX, wheelDeltaY} = event
|
||||
if Math.abs(wheelDeltaX) > Math.abs(wheelDeltaY)
|
||||
@refs.horizontalScrollbar.getDOMNode().scrollLeft -= wheelDeltaX
|
||||
else
|
||||
@refs.verticalScrollbar.getDOMNode().scrollTop -= wheelDeltaY
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
clearPreservedRowRange: ->
|
||||
@preservedRowRange = null
|
||||
@scrollingVertically = false
|
||||
@requestUpdate()
|
||||
|
||||
clearPreservedRowRangeAfterDelay: null # Created lazily
|
||||
|
||||
onBatchedUpdatesStarted: ->
|
||||
@batchingUpdates = true
|
||||
|
||||
onBatchedUpdatesEnded: ->
|
||||
updateRequested = @updateRequested
|
||||
@updateRequested = false
|
||||
@batchingUpdates = false
|
||||
if updateRequested
|
||||
@requestUpdate()
|
||||
|
||||
onScreenLinesChanged: (change) ->
|
||||
{editor} = @props
|
||||
@pendingChanges.push(change)
|
||||
@requestUpdate() if editor.intersectsVisibleRowRange(change.start, change.end + 1) # TODO: Use closed-open intervals for change events
|
||||
|
||||
onSelectionAdded: (selection) ->
|
||||
{editor} = @props
|
||||
@requestUpdate() if editor.selectionIntersectsVisibleRowRange(selection)
|
||||
|
||||
onScrollTopChanged: ->
|
||||
@preservedRowRange = @getRenderedRowRange()
|
||||
@scrollingVertically = true
|
||||
@clearPreservedRowRangeAfterDelay ?= debounce(@clearPreservedRowRange, 200)
|
||||
@clearPreservedRowRangeAfterDelay()
|
||||
@requestUpdate()
|
||||
|
||||
onSelectionRemoved: (selection) ->
|
||||
{editor} = @props
|
||||
@requestUpdate() if editor.selectionIntersectsVisibleRowRange(selection)
|
||||
|
||||
onCursorsMoved: ->
|
||||
@cursorsMoved = true
|
||||
|
||||
requestUpdate: ->
|
||||
if @batchingUpdates
|
||||
@updateRequested = true
|
||||
else
|
||||
@forceUpdate()
|
||||
|
||||
measureHeightAndWidth: ->
|
||||
@refs.scrollView.measureHeightAndWidth()
|
||||
|
||||
consolidateSelections: (e) ->
|
||||
e.abortKeyBinding() unless @props.editor.consolidateSelections()
|
||||
@@ -0,0 +1,198 @@
|
||||
React = require 'react'
|
||||
{div} = require 'reactionary'
|
||||
{debounce} = require 'underscore-plus'
|
||||
|
||||
InputComponent = require './input-component'
|
||||
LinesComponent = require './lines-component'
|
||||
CursorsComponent = require './cursors-component'
|
||||
SelectionsComponent = require './selections-component'
|
||||
|
||||
module.exports =
|
||||
EditorScrollViewComponent = React.createClass
|
||||
displayName: 'EditorScrollViewComponent'
|
||||
|
||||
measurementPending: false
|
||||
overflowChangedEventsPaused: false
|
||||
overflowChangedWhilePaused: false
|
||||
|
||||
render: ->
|
||||
{editor, fontSize, fontFamily, lineHeight, showIndentGuide} = @props
|
||||
{scrollHeight, scrollWidth, renderedRowRange, pendingChanges, scrollingVertically} = @props
|
||||
{cursorBlinkPeriod, cursorBlinkResumeDelay, cursorsMoved, onInputFocused, onInputBlurred} = @props
|
||||
|
||||
if @isMounted()
|
||||
inputStyle = @getHiddenInputPosition()
|
||||
inputStyle.WebkitTransform = 'translateZ(0)'
|
||||
|
||||
contentStyle =
|
||||
height: scrollHeight
|
||||
minWidth: scrollWidth
|
||||
WebkitTransform: "translate3d(#{-editor.getScrollLeft()}px, #{-editor.getScrollTop()}px, 0)"
|
||||
|
||||
div className: 'scroll-view',
|
||||
InputComponent
|
||||
ref: 'input'
|
||||
className: 'hidden-input'
|
||||
style: inputStyle
|
||||
onInput: @onInput
|
||||
onFocus: onInputFocused
|
||||
onBlur: onInputBlurred
|
||||
|
||||
div className: 'scroll-view-content', style: contentStyle, onMouseDown: @onMouseDown,
|
||||
CursorsComponent({editor, cursorsMoved, cursorBlinkPeriod, cursorBlinkResumeDelay})
|
||||
LinesComponent {
|
||||
ref: 'lines', editor, fontSize, fontFamily, lineHeight, showIndentGuide,
|
||||
renderedRowRange, pendingChanges, scrollingVertically
|
||||
}
|
||||
div className: 'underlayer',
|
||||
SelectionsComponent({editor})
|
||||
|
||||
componentDidMount: ->
|
||||
@getDOMNode().addEventListener 'overflowchanged', @onOverflowChanged
|
||||
window.addEventListener('resize', @onWindowResize)
|
||||
|
||||
@measureHeightAndWidth()
|
||||
|
||||
componentDidUnmount: ->
|
||||
window.removeEventListener('resize', @onWindowResize)
|
||||
|
||||
componentDidUpdate: ->
|
||||
@pauseOverflowChangedEvents()
|
||||
|
||||
onOverflowChanged: ->
|
||||
if @overflowChangedEventsPaused
|
||||
@overflowChangedWhilePaused = true
|
||||
else
|
||||
@requestMeasurement()
|
||||
|
||||
onWindowResize: ->
|
||||
@requestMeasurement()
|
||||
|
||||
pauseOverflowChangedEvents: ->
|
||||
@overflowChangedEventsPaused = true
|
||||
@resumeOverflowChangedEventsAfterDelay ?= debounce(@resumeOverflowChangedEvents, 500)
|
||||
@resumeOverflowChangedEventsAfterDelay()
|
||||
|
||||
resumeOverflowChangedEvents: ->
|
||||
if @overflowChangedWhilePaused
|
||||
@overflowChangedWhilePaused = false
|
||||
@requestMeasurement()
|
||||
|
||||
resumeOverflowChangedEventsAfterDelay: null
|
||||
|
||||
requestMeasurement: ->
|
||||
return if @measurementPending
|
||||
|
||||
@measurementPending = true
|
||||
requestAnimationFrame =>
|
||||
@measurementPending = false
|
||||
@measureHeightAndWidth()
|
||||
|
||||
onInput: (char, replaceLastCharacter) ->
|
||||
{editor} = @props
|
||||
|
||||
if replaceLastCharacter
|
||||
editor.transact ->
|
||||
editor.selectLeft()
|
||||
editor.insertText(char)
|
||||
else
|
||||
editor.insertText(char)
|
||||
|
||||
onMouseDown: (event) ->
|
||||
{editor} = @props
|
||||
{detail, shiftKey, metaKey} = event
|
||||
screenPosition = @screenPositionForMouseEvent(event)
|
||||
|
||||
if shiftKey
|
||||
editor.selectToScreenPosition(screenPosition)
|
||||
else if metaKey
|
||||
editor.addCursorAtScreenPosition(screenPosition)
|
||||
else
|
||||
editor.setCursorScreenPosition(screenPosition)
|
||||
switch detail
|
||||
when 2 then editor.selectWord()
|
||||
when 3 then editor.selectLine()
|
||||
|
||||
@selectToMousePositionUntilMouseUp(event)
|
||||
|
||||
selectToMousePositionUntilMouseUp: (event) ->
|
||||
{editor} = @props
|
||||
dragging = false
|
||||
lastMousePosition = {}
|
||||
|
||||
animationLoop = =>
|
||||
requestAnimationFrame =>
|
||||
if dragging
|
||||
@selectToMousePosition(lastMousePosition)
|
||||
animationLoop()
|
||||
|
||||
onMouseMove = (event) ->
|
||||
lastMousePosition.clientX = event.clientX
|
||||
lastMousePosition.clientY = event.clientY
|
||||
|
||||
# Start the animation loop when the mouse moves prior to a mouseup event
|
||||
unless dragging
|
||||
dragging = true
|
||||
animationLoop()
|
||||
|
||||
# Stop dragging when cursor enters dev tools because we can't detect mouseup
|
||||
onMouseUp() if event.which is 0
|
||||
|
||||
onMouseUp = ->
|
||||
dragging = false
|
||||
window.removeEventListener('mousemove', onMouseMove)
|
||||
window.removeEventListener('mouseup', onMouseUp)
|
||||
editor.finalizeSelections()
|
||||
|
||||
window.addEventListener('mousemove', onMouseMove)
|
||||
window.addEventListener('mouseup', onMouseUp)
|
||||
|
||||
selectToMousePosition: (event) ->
|
||||
@props.editor.selectToScreenPosition(@screenPositionForMouseEvent(event))
|
||||
|
||||
screenPositionForMouseEvent: (event) ->
|
||||
pixelPosition = @pixelPositionForMouseEvent(event)
|
||||
@props.editor.screenPositionForPixelPosition(pixelPosition)
|
||||
|
||||
pixelPositionForMouseEvent: (event) ->
|
||||
{editor} = @props
|
||||
{clientX, clientY} = event
|
||||
|
||||
editorClientRect = @getDOMNode().getBoundingClientRect()
|
||||
top = clientY - editorClientRect.top + editor.getScrollTop()
|
||||
left = clientX - editorClientRect.left + editor.getScrollLeft()
|
||||
{top, left}
|
||||
|
||||
getHiddenInputPosition: ->
|
||||
{editor} = @props
|
||||
return {top: 0, left: 0} unless @isMounted() and editor.getCursor()?
|
||||
|
||||
{top, left, height, width} = editor.getCursor().getPixelRect()
|
||||
top = top - editor.getScrollTop()
|
||||
top = Math.max(0, Math.min(editor.getHeight() - height, top))
|
||||
left = left - editor.getScrollLeft()
|
||||
left = Math.max(0, Math.min(editor.getWidth() - width, left))
|
||||
|
||||
{top, left}
|
||||
|
||||
# Measure explicitly-styled height and width and relay them to the model. If
|
||||
# these values aren't explicitly styled, we assume the editor is unconstrained
|
||||
# and use the scrollHeight / scrollWidth as its height and width in
|
||||
# calculations.
|
||||
measureHeightAndWidth: ->
|
||||
return unless @isMounted()
|
||||
|
||||
node = @getDOMNode()
|
||||
computedStyle = getComputedStyle(node)
|
||||
{editor} = @props
|
||||
|
||||
unless computedStyle.height is '0px'
|
||||
clientHeight = node.clientHeight
|
||||
editor.setHeight(clientHeight) if clientHeight > 0
|
||||
|
||||
unless computedStyle.width is '0px'
|
||||
clientWidth = node.clientWidth
|
||||
editor.setWidth(clientWidth) if clientHeight > 0
|
||||
|
||||
focus: ->
|
||||
@refs.input.focus()
|
||||
@@ -50,7 +50,7 @@ class EditorView extends View
|
||||
showLineNumbers: true
|
||||
autoIndent: true
|
||||
normalizeIndentOnPaste: true
|
||||
nonWordCharacters: "./\\()\"':,.;<>~!@#$%^&*|+=[]{}`~?-"
|
||||
nonWordCharacters: "./\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
|
||||
preferredLineLength: 80
|
||||
tabLength: 2
|
||||
softWrap: false
|
||||
@@ -1482,16 +1482,13 @@ class EditorView extends View
|
||||
html = @buildEmptyLineHtml(showIndentGuide, eolInvisibles, htmlEolInvisibles, indentation, editor, mini)
|
||||
line.push(html) if html
|
||||
else
|
||||
firstNonWhitespacePosition = text.search(/\S/)
|
||||
firstTrailingWhitespacePosition = text.search(/\s*$/)
|
||||
lineIsWhitespaceOnly = firstTrailingWhitespacePosition is 0
|
||||
position = 0
|
||||
for token in tokens
|
||||
@updateScopeStack(line, scopeStack, token.scopes)
|
||||
hasLeadingWhitespace = position < firstNonWhitespacePosition
|
||||
hasTrailingWhitespace = position + token.value.length > firstTrailingWhitespacePosition
|
||||
hasIndentGuide = not mini and showIndentGuide and (hasLeadingWhitespace or lineIsWhitespaceOnly)
|
||||
line.push(token.getValueAsHtml({invisibles, hasLeadingWhitespace, hasTrailingWhitespace, hasIndentGuide}))
|
||||
hasIndentGuide = not mini and showIndentGuide and token.hasLeadingWhitespace or (token.hasTrailingWhitespace and lineIsWhitespaceOnly)
|
||||
line.push(token.getValueAsHtml({invisibles, hasIndentGuide}))
|
||||
position += token.value.length
|
||||
|
||||
@popScope(line, scopeStack) while scopeStack.length > 0
|
||||
@@ -1519,7 +1516,7 @@ class EditorView extends View
|
||||
|
||||
@pushScope: (line, scopeStack, scope) ->
|
||||
scopeStack.push(scope)
|
||||
line.push("<span class=\"#{scope.replace(/\./g, ' ')}\">")
|
||||
line.push("<span class=\"#{scope.replace(/\.+/g, ' ')}\">")
|
||||
|
||||
@popScope: (line, scopeStack) ->
|
||||
scopeStack.pop()
|
||||
|
||||
+148
-35
@@ -136,10 +136,6 @@ class Editor extends Model
|
||||
atom.deserializers.add(this)
|
||||
Delegator.includeInto(this)
|
||||
|
||||
@properties
|
||||
scrollTop: 0
|
||||
scrollLeft: 0
|
||||
|
||||
deserializing: false
|
||||
callDisplayBufferCreatedHook: false
|
||||
registerEditor: false
|
||||
@@ -153,6 +149,9 @@ class Editor extends Model
|
||||
'autoDecreaseIndentForBufferRow', 'toggleLineCommentForBufferRow', 'toggleLineCommentsForBufferRows',
|
||||
toProperty: 'languageMode'
|
||||
|
||||
@delegatesProperties '$lineHeight', '$defaultCharWidth', '$height', '$width',
|
||||
'$scrollTop', '$scrollLeft', 'manageScrollPosition', toProperty: 'displayBuffer'
|
||||
|
||||
constructor: ({@softTabs, initialLine, tabLength, softWrap, @displayBuffer, buffer, registerEditor, suppressCursorCreation}) ->
|
||||
super
|
||||
|
||||
@@ -217,7 +216,10 @@ class Editor extends Model
|
||||
@subscribe @displayBuffer, 'soft-wrap-changed', (args...) => @emit 'soft-wrap-changed', args...
|
||||
|
||||
getViewClass: ->
|
||||
require './editor-view'
|
||||
if atom.config.get('core.useReactEditor')
|
||||
require './react-editor-view'
|
||||
else
|
||||
require './editor-view'
|
||||
|
||||
destroyed: ->
|
||||
@unsubscribe()
|
||||
@@ -232,8 +234,6 @@ class Editor extends Model
|
||||
displayBuffer = @displayBuffer.copy()
|
||||
softTabs = @getSoftTabs()
|
||||
newEditor = new Editor({@buffer, displayBuffer, tabLength, softTabs, suppressCursorCreation: true, registerEditor: true})
|
||||
newEditor.setScrollTop(@getScrollTop())
|
||||
newEditor.setScrollLeft(@getScrollLeft())
|
||||
for marker in @findMarkers(editorId: @id)
|
||||
marker.copy(editorId: newEditor.id, preserveFolds: true)
|
||||
newEditor
|
||||
@@ -269,18 +269,6 @@ class Editor extends Model
|
||||
# Controls visiblity based on the given {Boolean}.
|
||||
setVisible: (visible) -> @displayBuffer.setVisible(visible)
|
||||
|
||||
# Called by {EditorView} when the scroll position changes so it can be
|
||||
# persisted across reloads.
|
||||
setScrollTop: (@scrollTop) -> @scrollTop
|
||||
|
||||
getScrollTop: -> @scrollTop
|
||||
|
||||
# Called by {EditorView} when the scroll position changes so it can be
|
||||
# persisted across reloads.
|
||||
setScrollLeft: (@scrollLeft) -> @scrollLeft
|
||||
|
||||
getScrollLeft: -> @scrollLeft
|
||||
|
||||
# Set the number of characters that can be displayed horizontally in the
|
||||
# editor.
|
||||
#
|
||||
@@ -301,6 +289,9 @@ class Editor extends Model
|
||||
# softTabs - A {Boolean}
|
||||
setSoftTabs: (@softTabs) -> @softTabs
|
||||
|
||||
# Public: Toggle soft tabs for this editor
|
||||
toggleSoftTabs: -> @setSoftTabs(not @getSoftTabs())
|
||||
|
||||
# Public: Get whether soft wrap is enabled for this editor.
|
||||
getSoftWrap: -> @displayBuffer.getSoftWrap()
|
||||
|
||||
@@ -309,6 +300,9 @@ class Editor extends Model
|
||||
# softWrap - A {Boolean}
|
||||
setSoftWrap: (softWrap) -> @displayBuffer.setSoftWrap(softWrap)
|
||||
|
||||
# Public: Toggle soft wrap for this editor
|
||||
toggleSoftWrap: -> @setSoftWrap(not @getSoftWrap())
|
||||
|
||||
# Public: Get the text representing a single level of indent.
|
||||
#
|
||||
# If soft tabs are enabled, the text is composed of N spaces, where N is the
|
||||
@@ -381,7 +375,7 @@ class Editor extends Model
|
||||
else
|
||||
endColumn = @lineForBufferRow(bufferRow).match(/^\s*/)[0].length
|
||||
newIndentString = @buildIndentString(newLevel)
|
||||
@buffer.change([[bufferRow, 0], [bufferRow, endColumn]], newIndentString)
|
||||
@buffer.setTextInRange([[bufferRow, 0], [bufferRow, endColumn]], newIndentString)
|
||||
|
||||
# Public: Get the indentation level of the given line of text.
|
||||
#
|
||||
@@ -394,13 +388,7 @@ class Editor extends Model
|
||||
#
|
||||
# Returns a {Number}.
|
||||
indentLevelForLine: (line) ->
|
||||
if match = line.match(/^[\t ]+/)
|
||||
leadingWhitespace = match[0]
|
||||
tabCount = leadingWhitespace.match(/\t/g)?.length ? 0
|
||||
spaceCount = leadingWhitespace.match(/[ ]/g)?.length ? 0
|
||||
tabCount + (spaceCount / @getTabLength())
|
||||
else
|
||||
0
|
||||
@displayBuffer.indentLevelForLine(line)
|
||||
|
||||
# Constructs the string used for tabs.
|
||||
buildIndentString: (number) ->
|
||||
@@ -421,6 +409,15 @@ class Editor extends Model
|
||||
# filePath - A {String} path.
|
||||
saveAs: (filePath) -> @buffer.saveAs(filePath)
|
||||
|
||||
checkoutHead: ->
|
||||
if filePath = @getPath()
|
||||
atom.project.getRepo()?.checkoutHead(filePath)
|
||||
|
||||
# Copies the current file path to the native clipboard.
|
||||
copyPathToClipboard: ->
|
||||
if filePath = @getPath()
|
||||
atom.clipboard.write(filePath)
|
||||
|
||||
# Public: Returns the {String} path of this editor's text buffer.
|
||||
getPath: -> @buffer.getPath()
|
||||
|
||||
@@ -456,8 +453,8 @@ class Editor extends Model
|
||||
# {Delegates to: TextBuffer.nextNonBlankRow}
|
||||
nextNonBlankBufferRow: (bufferRow) -> @buffer.nextNonBlankRow(bufferRow)
|
||||
|
||||
# {Delegates to: TextBuffer.getEofPosition}
|
||||
getEofBufferPosition: -> @buffer.getEofPosition()
|
||||
# {Delegates to: TextBuffer.getEndPosition}
|
||||
getEofBufferPosition: -> @buffer.getEndPosition()
|
||||
|
||||
# Public: Returns a {Number} representing the last zero-indexed buffer row
|
||||
# number of the editor.
|
||||
@@ -599,6 +596,9 @@ class Editor extends Model
|
||||
# Returns an {Array} of {String}s.
|
||||
getCursorScopes: -> @getCursor().getScopes()
|
||||
|
||||
logCursorScope: ->
|
||||
console.log @getCursorScopes()
|
||||
|
||||
# Public: For each selection, replace the selected text with the given text.
|
||||
#
|
||||
# text - A {String} representing the text to insert.
|
||||
@@ -913,7 +913,7 @@ class Editor extends Model
|
||||
for foldedRow in foldedRows when 0 <= foldedRow <= @getLastBufferRow()
|
||||
@foldBufferRow(foldedRow)
|
||||
|
||||
@setSelectedBufferRange(selection.translate([-insertDelta]), preserveFolds: true)
|
||||
@setSelectedBufferRange(selection.translate([-insertDelta]), preserveFolds: true, autoscroll: true)
|
||||
|
||||
# Move lines intersecting the most recent selection down by one row in screen
|
||||
# coordinates.
|
||||
@@ -969,7 +969,7 @@ class Editor extends Model
|
||||
for foldedRow in foldedRows when 0 <= foldedRow <= @getLastBufferRow()
|
||||
@foldBufferRow(foldedRow)
|
||||
|
||||
@setSelectedBufferRange(selection.translate([insertDelta]), preserveFolds: true)
|
||||
@setSelectedBufferRange(selection.translate([insertDelta]), preserveFolds: true, autoscroll: true)
|
||||
|
||||
# Duplicate the most recent cursor's current line.
|
||||
duplicateLines: ->
|
||||
@@ -1002,6 +1002,12 @@ class Editor extends Model
|
||||
deprecate("Use Editor::duplicateLines() instead")
|
||||
@duplicateLines()
|
||||
|
||||
# Public: Mutate the text of all the selections in a single transaction.
|
||||
#
|
||||
# All the changes made inside the given {Function} can be reverted with a
|
||||
# single call to {::undo}.
|
||||
#
|
||||
# fn - A {Function} that will be called with each {Selection}.
|
||||
mutateSelectedText: (fn) ->
|
||||
@transact => fn(selection) for selection in @getSelections()
|
||||
|
||||
@@ -1172,6 +1178,16 @@ class Editor extends Model
|
||||
setSelectedBufferRange: (bufferRange, options) ->
|
||||
@setSelectedBufferRanges([bufferRange], options)
|
||||
|
||||
# Public: Set the selected range in screen coordinates. If there are multiple
|
||||
# selections, they are reduced to a single selection with the given range.
|
||||
#
|
||||
# screenRange - A {Range} or range-compatible {Array}.
|
||||
# options - An options {Object}:
|
||||
# :reversed - A {Boolean} indicating whether to create the selection in a
|
||||
# reversed orientation.
|
||||
setSelectedScreenRange: (screenRange, options) ->
|
||||
@setSelectedBufferRange(@bufferRangeForScreenRange(screenRange, options), options)
|
||||
|
||||
# Public: Set the selected ranges in buffer coordinates. If there are multiple
|
||||
# selections, they are replaced by new selections with the given ranges.
|
||||
#
|
||||
@@ -1196,6 +1212,7 @@ class Editor extends Model
|
||||
# Remove the given selection.
|
||||
removeSelection: (selection) ->
|
||||
_.remove(@selections, selection)
|
||||
@emit 'selection-removed', selection
|
||||
|
||||
# Reduce one or more selections to a single empty selection based on the most
|
||||
# recently added cursor.
|
||||
@@ -1212,6 +1229,9 @@ class Editor extends Model
|
||||
else
|
||||
false
|
||||
|
||||
selectionScreenRangeChanged: (selection) ->
|
||||
@emit 'selection-screen-range-changed', selection
|
||||
|
||||
# Public: Get current {Selection}s.
|
||||
#
|
||||
# Returns: An {Array} of {Selection}s.
|
||||
@@ -1322,6 +1342,14 @@ class Editor extends Model
|
||||
getSelectedBufferRanges: ->
|
||||
selection.getBufferRange() for selection in @getSelectionsOrderedByBufferPosition()
|
||||
|
||||
# Public: Get the {Range}s of all selections in screen coordinates.
|
||||
#
|
||||
# The ranges are sorted by their position in the buffer.
|
||||
#
|
||||
# Returns an {Array} of {Range}s.
|
||||
getSelectedScreenRanges: ->
|
||||
selection.getScreenRange() for selection in @getSelectionsOrderedByBufferPosition()
|
||||
|
||||
# Public: Get the selected text of the most recently added selection.
|
||||
#
|
||||
# Returns a {String}.
|
||||
@@ -1342,7 +1370,7 @@ class Editor extends Model
|
||||
# text - A {String}
|
||||
#
|
||||
# Returns the {Range} of the newly-inserted text.
|
||||
setTextInBufferRange: (range, text) -> @getBuffer().change(range, text)
|
||||
setTextInBufferRange: (range, text) -> @getBuffer().setTextInRange(range, text)
|
||||
|
||||
# Public: Get the {Range} of the paragraph surrounding the most recently added
|
||||
# cursor.
|
||||
@@ -1425,9 +1453,26 @@ class Editor extends Model
|
||||
moveCursorToNextWordBoundary: ->
|
||||
@moveCursors (cursor) -> cursor.moveToNextWordBoundary()
|
||||
|
||||
scrollToCursorPosition: ->
|
||||
@getCursor().autoscroll()
|
||||
|
||||
pageUp: ->
|
||||
@setScrollTop(@getScrollTop() - @getHeight())
|
||||
|
||||
pageDown: ->
|
||||
@setScrollTop(@getScrollTop() + @getHeight())
|
||||
|
||||
moveCursors: (fn) ->
|
||||
fn(cursor) for cursor in @getCursors()
|
||||
@mergeCursors()
|
||||
@movingCursors = true
|
||||
@batchUpdates =>
|
||||
fn(cursor) for cursor in @getCursors()
|
||||
@mergeCursors()
|
||||
@movingCursors = false
|
||||
@emit 'cursors-moved'
|
||||
|
||||
cursorMoved: (event) ->
|
||||
@emit 'cursor-moved', event
|
||||
@emit 'cursors-moved' unless @movingCursors
|
||||
|
||||
# Public: Select from the current cursor position to the given position in
|
||||
# screen coordinates.
|
||||
@@ -1729,7 +1774,9 @@ class Editor extends Model
|
||||
# execution and revert any changes performed up to the abortion.
|
||||
#
|
||||
# fn - A {Function} to call inside the transaction.
|
||||
transact: (fn) -> @buffer.transact(fn)
|
||||
transact: (fn) ->
|
||||
@batchUpdates =>
|
||||
@buffer.transact(fn)
|
||||
|
||||
# Public: Start an open-ended transaction.
|
||||
#
|
||||
@@ -1749,6 +1796,12 @@ class Editor extends Model
|
||||
# within the transaction.
|
||||
abortTransaction: -> @buffer.abortTransaction()
|
||||
|
||||
batchUpdates: (fn) ->
|
||||
@emit 'batched-updates-started'
|
||||
result = fn()
|
||||
@emit 'batched-updates-ended'
|
||||
result
|
||||
|
||||
inspect: ->
|
||||
"<Editor #{@id}>"
|
||||
|
||||
@@ -1765,6 +1818,66 @@ class Editor extends Model
|
||||
getSelectionMarkerAttributes: ->
|
||||
type: 'selection', editorId: @id, invalidate: 'never'
|
||||
|
||||
getVerticalScrollMargin: -> @displayBuffer.getVerticalScrollMargin()
|
||||
setVerticalScrollMargin: (verticalScrollMargin) -> @displayBuffer.setVerticalScrollMargin(verticalScrollMargin)
|
||||
|
||||
getHorizontalScrollMargin: -> @displayBuffer.getHorizontalScrollMargin()
|
||||
setHorizontalScrollMargin: (horizontalScrollMargin) -> @displayBuffer.setHorizontalScrollMargin(horizontalScrollMargin)
|
||||
|
||||
getLineHeight: -> @displayBuffer.getLineHeight()
|
||||
setLineHeight: (lineHeight) -> @displayBuffer.setLineHeight(lineHeight)
|
||||
|
||||
getScopedCharWidth: (scopeNames, char) -> @displayBuffer.getScopedCharWidth(scopeNames, char)
|
||||
setScopedCharWidth: (scopeNames, char, width) -> @displayBuffer.setScopedCharWidth(scopeNames, char, width)
|
||||
|
||||
getScopedCharWidths: (scopeNames) -> @displayBuffer.getScopedCharWidths(scopeNames)
|
||||
|
||||
clearScopedCharWidths: -> @displayBuffer.clearScopedCharWidths()
|
||||
|
||||
getDefaultCharWidth: -> @displayBuffer.getDefaultCharWidth()
|
||||
setDefaultCharWidth: (defaultCharWidth) -> @displayBuffer.setDefaultCharWidth(defaultCharWidth)
|
||||
|
||||
setHeight: (height) -> @displayBuffer.setHeight(height)
|
||||
getHeight: -> @displayBuffer.getHeight()
|
||||
|
||||
setWidth: (width) -> @displayBuffer.setWidth(width)
|
||||
getWidth: -> @displayBuffer.getWidth()
|
||||
|
||||
getScrollTop: -> @displayBuffer.getScrollTop()
|
||||
setScrollTop: (scrollTop) -> @displayBuffer.setScrollTop(scrollTop)
|
||||
|
||||
getScrollBottom: -> @displayBuffer.getScrollBottom()
|
||||
setScrollBottom: (scrollBottom) -> @displayBuffer.setScrollBottom(scrollBottom)
|
||||
|
||||
getScrollLeft: -> @displayBuffer.getScrollLeft()
|
||||
setScrollLeft: (scrollLeft) -> @displayBuffer.setScrollLeft(scrollLeft)
|
||||
|
||||
getScrollRight: -> @displayBuffer.getScrollRight()
|
||||
setScrollRight: (scrollRight) -> @displayBuffer.setScrollRight(scrollRight)
|
||||
|
||||
getScrollHeight: -> @displayBuffer.getScrollHeight()
|
||||
getScrollWidth: (scrollWidth) -> @displayBuffer.getScrollWidth(scrollWidth)
|
||||
|
||||
getVisibleRowRange: -> @displayBuffer.getVisibleRowRange()
|
||||
|
||||
intersectsVisibleRowRange: (startRow, endRow) -> @displayBuffer.intersectsVisibleRowRange(startRow, endRow)
|
||||
|
||||
selectionIntersectsVisibleRowRange: (selection) -> @displayBuffer.selectionIntersectsVisibleRowRange(selection)
|
||||
|
||||
pixelPositionForScreenPosition: (screenPosition) -> @displayBuffer.pixelPositionForScreenPosition(screenPosition)
|
||||
|
||||
pixelPositionForBufferPosition: (bufferPosition) -> @displayBuffer.pixelPositionForBufferPosition(bufferPosition)
|
||||
|
||||
screenPositionForPixelPosition: (pixelPosition) -> @displayBuffer.screenPositionForPixelPosition(pixelPosition)
|
||||
|
||||
pixelRectForScreenRange: (screenRange) -> @displayBuffer.pixelRectForScreenRange(screenRange)
|
||||
|
||||
scrollToScreenRange: (screenRange) -> @displayBuffer.scrollToScreenRange(screenRange)
|
||||
|
||||
scrollToScreenPosition: (screenPosition) -> @displayBuffer.scrollToScreenPosition(screenPosition)
|
||||
|
||||
scrollToBufferPosition: (bufferPosition) -> @displayBuffer.scrollToBufferPosition(bufferPosition)
|
||||
|
||||
# Deprecated: Call {::joinLines} instead.
|
||||
joinLine: ->
|
||||
deprecate("Use Editor::joinLines() instead")
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
React = require 'react'
|
||||
{div} = require 'reactionary'
|
||||
{isEqual, isEqualForProperties, multiplyString} = require 'underscore-plus'
|
||||
SubscriberMixin = require './subscriber-mixin'
|
||||
|
||||
module.exports =
|
||||
GutterComponent = React.createClass
|
||||
displayName: 'GutterComponent'
|
||||
mixins: [SubscriberMixin]
|
||||
|
||||
render: ->
|
||||
div className: 'gutter',
|
||||
@renderLineNumbers() if @isMounted()
|
||||
|
||||
renderLineNumbers: ->
|
||||
{editor, renderedRowRange, scrollTop, scrollHeight} = @props
|
||||
[startRow, endRow] = renderedRowRange
|
||||
charWidth = editor.getDefaultCharWidth()
|
||||
lineHeight = editor.getLineHeight()
|
||||
maxDigits = editor.getLastBufferRow().toString().length
|
||||
style =
|
||||
width: charWidth * (maxDigits + 1.5)
|
||||
height: scrollHeight
|
||||
WebkitTransform: "translate3d(0, #{-scrollTop}px, 0)"
|
||||
|
||||
lineNumbers = []
|
||||
tokenizedLines = editor.linesForScreenRows(startRow, endRow - 1)
|
||||
tokenizedLines.push({id: 0}) if tokenizedLines.length is 0
|
||||
for bufferRow, i in editor.bufferRowsForScreenRows(startRow, endRow - 1)
|
||||
if bufferRow is lastBufferRow
|
||||
lineNumber = '•'
|
||||
else
|
||||
lastBufferRow = bufferRow
|
||||
lineNumber = (bufferRow + 1).toString()
|
||||
|
||||
key = tokenizedLines[i].id
|
||||
screenRow = startRow + i
|
||||
lineNumbers.push(LineNumberComponent({key, lineNumber, maxDigits, bufferRow, screenRow, lineHeight}))
|
||||
lastBufferRow = bufferRow
|
||||
|
||||
div className: 'line-numbers', style: style,
|
||||
lineNumbers
|
||||
|
||||
# Only update the gutter if the visible row range has changed or if a
|
||||
# non-zero-delta change to the screen lines has occurred within the current
|
||||
# visible row range.
|
||||
shouldComponentUpdate: (newProps) ->
|
||||
return true unless isEqualForProperties(newProps, @props, 'renderedRowRange', 'scrollTop', 'lineHeight')
|
||||
|
||||
{renderedRowRange, pendingChanges} = newProps
|
||||
for change in pendingChanges when change.screenDelta > 0 or change.bufferDelta > 0
|
||||
return true unless change.end <= renderedRowRange.start or renderedRowRange.end <= change.start
|
||||
|
||||
false
|
||||
|
||||
LineNumberComponent = React.createClass
|
||||
displayName: 'LineNumberComponent'
|
||||
|
||||
render: ->
|
||||
{bufferRow, screenRow, lineHeight} = @props
|
||||
div
|
||||
className: "line-number line-number-#{bufferRow}"
|
||||
style: {top: screenRow * lineHeight}
|
||||
'data-buffer-row': bufferRow
|
||||
'data-screen-row': screenRow
|
||||
dangerouslySetInnerHTML: {__html: @buildInnerHTML()}
|
||||
|
||||
buildInnerHTML: ->
|
||||
{lineNumber, maxDigits} = @props
|
||||
if lineNumber.length < maxDigits
|
||||
padding = multiplyString(' ', maxDigits - lineNumber.length)
|
||||
padding + lineNumber + @iconDivHTML
|
||||
else
|
||||
lineNumber + @iconDivHTML
|
||||
|
||||
iconDivHTML: '<div class="icon-right"></div>'
|
||||
|
||||
shouldComponentUpdate: (newProps) ->
|
||||
not isEqualForProperties(newProps, @props, 'lineHeight', 'screenRow')
|
||||
@@ -0,0 +1,51 @@
|
||||
punycode = require 'punycode'
|
||||
{last, isEqual} = require 'underscore-plus'
|
||||
React = require 'react'
|
||||
{input} = require 'reactionary'
|
||||
|
||||
module.exports =
|
||||
InputComponent = React.createClass
|
||||
displayName: 'InputComponent'
|
||||
|
||||
render: ->
|
||||
{className, style, onFocus, onBlur} = @props
|
||||
|
||||
input {className, style, onFocus, onBlur}
|
||||
|
||||
getInitialState: ->
|
||||
{lastChar: ''}
|
||||
|
||||
componentDidMount: ->
|
||||
@getDOMNode().addEventListener 'input', @onInput
|
||||
@getDOMNode().addEventListener 'compositionupdate', @onCompositionUpdate
|
||||
|
||||
# Don't let text accumulate in the input forever, but avoid excessive reflows
|
||||
componentDidUpdate: ->
|
||||
if @lastValueLength > 500 and not @isPressAndHoldCharacter(@state.lastChar)
|
||||
@getDOMNode().value = ''
|
||||
@lastValueLength = 0
|
||||
|
||||
# This should actually consult the property lists in /System/Library/Input Methods/PressAndHold.app
|
||||
isPressAndHoldCharacter: (char) ->
|
||||
@state.lastChar.match /[aeiouAEIOU]/
|
||||
|
||||
shouldComponentUpdate: (newProps) ->
|
||||
not isEqual(newProps.style, @props.style)
|
||||
|
||||
onInput: (e) ->
|
||||
e.stopPropagation()
|
||||
valueCharCodes = punycode.ucs2.decode(@getDOMNode().value)
|
||||
valueLength = valueCharCodes.length
|
||||
replaceLastChar = valueLength is @lastValueLength
|
||||
@lastValueLength = valueLength
|
||||
lastChar = String.fromCharCode(last(valueCharCodes))
|
||||
@props.onInput?(lastChar, replaceLastChar)
|
||||
|
||||
onFocus: ->
|
||||
@props.onFocus?()
|
||||
|
||||
onBlur: ->
|
||||
@props.onBlur?()
|
||||
|
||||
focus: ->
|
||||
@getDOMNode().focus()
|
||||
@@ -53,11 +53,11 @@ class LanguageMode
|
||||
buffer.transact ->
|
||||
columnStart = startMatch[1].length
|
||||
columnEnd = columnStart + startMatch[2].length
|
||||
buffer.change([[start, columnStart], [start, columnEnd]], "")
|
||||
buffer.setTextInRange([[start, columnStart], [start, columnEnd]], "")
|
||||
|
||||
endLength = buffer.lineLengthForRow(end) - endMatch[2].length
|
||||
endColumn = endLength - endMatch[1].length
|
||||
buffer.change([[end, endColumn], [end, endLength]], "")
|
||||
buffer.setTextInRange([[end, endColumn], [end, endLength]], "")
|
||||
else
|
||||
buffer.transact ->
|
||||
buffer.insert([start, 0], commentStartString)
|
||||
@@ -72,7 +72,7 @@ class LanguageMode
|
||||
if match = commentStartRegex.search(buffer.lineForRow(row))
|
||||
columnStart = match[1].length
|
||||
columnEnd = columnStart + match[2].length
|
||||
buffer.change([[row, columnStart], [row, columnEnd]], "")
|
||||
buffer.setTextInRange([[row, columnStart], [row, columnEnd]], "")
|
||||
else
|
||||
if start is end
|
||||
indent = @editor.indentationForBufferRow(start)
|
||||
@@ -86,7 +86,7 @@ class LanguageMode
|
||||
if indentLength = line.match(indentRegex)?[0].length
|
||||
buffer.insert([row, indentLength], commentStartString)
|
||||
else
|
||||
buffer.change([[row, 0], [row, indentString.length]], indentString + commentStartString)
|
||||
buffer.setTextInRange([[row, 0], [row, indentString.length]], indentString + commentStartString)
|
||||
|
||||
# Folds all the foldable lines in the buffer.
|
||||
foldAll: ->
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
path = require 'path'
|
||||
os = require 'os'
|
||||
fs = require 'fs-plus'
|
||||
LessCache = require 'less-cache'
|
||||
{Subscriber} = require 'emissary'
|
||||
|
||||
tmpDir = if process.platform is 'win32' then os.tmpdir() else '/tmp'
|
||||
|
||||
# {LessCache} wrapper used by {ThemeManager} to read stylesheets.
|
||||
module.exports =
|
||||
class LessCompileCache
|
||||
Subscriber.includeInto(this)
|
||||
|
||||
@cacheDir: path.join(tmpDir, 'atom-compile-cache', 'less')
|
||||
@cacheDir: path.join(atom.getConfigDirPath(), 'compile-cache', 'less')
|
||||
|
||||
constructor: ({resourcePath, importPaths}) ->
|
||||
@lessSearchPaths = [
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
React = require 'react'
|
||||
{div, span} = require 'reactionary'
|
||||
{debounce, isEqual, isEqualForProperties, multiplyString} = require 'underscore-plus'
|
||||
{$$} = require 'space-pen'
|
||||
|
||||
DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0]
|
||||
AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT}
|
||||
|
||||
module.exports =
|
||||
LinesComponent = React.createClass
|
||||
displayName: 'LinesComponent'
|
||||
|
||||
render: ->
|
||||
if @isMounted()
|
||||
{editor, renderedRowRange, lineHeight, showIndentGuide} = @props
|
||||
[startRow, endRow] = renderedRowRange
|
||||
|
||||
lines =
|
||||
for tokenizedLine, i in editor.linesForScreenRows(startRow, endRow - 1)
|
||||
LineComponent({key: tokenizedLine.id, tokenizedLine, showIndentGuide, lineHeight, screenRow: startRow + i})
|
||||
|
||||
div {className: 'lines'}, lines
|
||||
|
||||
componentWillMount: ->
|
||||
@measuredLines = new WeakSet
|
||||
|
||||
componentDidMount: ->
|
||||
@measureLineHeightAndCharWidth()
|
||||
|
||||
shouldComponentUpdate: (newProps) ->
|
||||
return true unless isEqualForProperties(newProps, @props, 'renderedRowRange', 'fontSize', 'fontFamily', 'lineHeight', 'showIndentGuide')
|
||||
|
||||
{renderedRowRange, pendingChanges} = newProps
|
||||
for change in pendingChanges
|
||||
return true unless change.end <= renderedRowRange.start or renderedRowRange.end <= change.start
|
||||
|
||||
false
|
||||
|
||||
componentDidUpdate: (prevProps) ->
|
||||
@measureLineHeightAndCharWidth() unless isEqualForProperties(prevProps, @props, 'fontSize', 'fontFamily', 'lineHeight')
|
||||
@clearScopedCharWidths() unless isEqualForProperties(prevProps, @props, 'fontSize', 'fontFamily')
|
||||
@measureCharactersInNewLines() unless @props.scrollingVertically
|
||||
|
||||
measureLineHeightAndCharWidth: ->
|
||||
node = @getDOMNode()
|
||||
node.appendChild(DummyLineNode)
|
||||
lineHeight = DummyLineNode.getBoundingClientRect().height
|
||||
charWidth = DummyLineNode.firstChild.getBoundingClientRect().width
|
||||
node.removeChild(DummyLineNode)
|
||||
|
||||
{editor} = @props
|
||||
editor.setLineHeight(lineHeight)
|
||||
editor.setDefaultCharWidth(charWidth)
|
||||
|
||||
measureCharactersInNewLines: ->
|
||||
[visibleStartRow, visibleEndRow] = @props.renderedRowRange
|
||||
node = @getDOMNode()
|
||||
|
||||
for tokenizedLine, i in @props.editor.linesForScreenRows(visibleStartRow, visibleEndRow - 1)
|
||||
unless @measuredLines.has(tokenizedLine)
|
||||
lineNode = node.children[i]
|
||||
@measureCharactersInLine(tokenizedLine, lineNode)
|
||||
|
||||
measureCharactersInLine: (tokenizedLine, lineNode) ->
|
||||
{editor} = @props
|
||||
rangeForMeasurement = null
|
||||
iterator = null
|
||||
charIndex = 0
|
||||
|
||||
for {value, scopes}, tokenIndex in tokenizedLine.tokens
|
||||
charWidths = editor.getScopedCharWidths(scopes)
|
||||
|
||||
for char in value
|
||||
unless charWidths[char]?
|
||||
unless textNode?
|
||||
rangeForMeasurement ?= document.createRange()
|
||||
iterator = document.createNodeIterator(lineNode, NodeFilter.SHOW_TEXT, AcceptFilter)
|
||||
textNode = iterator.nextNode()
|
||||
textNodeIndex = 0
|
||||
nextTextNodeIndex = textNode.textContent.length
|
||||
|
||||
while nextTextNodeIndex <= charIndex
|
||||
textNode = iterator.nextNode()
|
||||
textNodeIndex = nextTextNodeIndex
|
||||
nextTextNodeIndex = textNodeIndex + textNode.textContent.length
|
||||
|
||||
i = charIndex - textNodeIndex
|
||||
rangeForMeasurement.setStart(textNode, i)
|
||||
rangeForMeasurement.setEnd(textNode, i + 1)
|
||||
charWidth = rangeForMeasurement.getBoundingClientRect().width
|
||||
editor.setScopedCharWidth(scopes, char, charWidth)
|
||||
|
||||
charIndex++
|
||||
|
||||
@measuredLines.add(tokenizedLine)
|
||||
|
||||
clearScopedCharWidths: ->
|
||||
@measuredLines.clear()
|
||||
@props.editor.clearScopedCharWidths()
|
||||
|
||||
|
||||
LineComponent = React.createClass
|
||||
displayName: 'LineComponent'
|
||||
|
||||
render: ->
|
||||
{screenRow, lineHeight} = @props
|
||||
|
||||
style =
|
||||
top: screenRow * lineHeight
|
||||
position: 'absolute'
|
||||
|
||||
div className: 'line', style: style, 'data-screen-row': screenRow, dangerouslySetInnerHTML: {__html: @buildInnerHTML()}
|
||||
|
||||
buildInnerHTML: ->
|
||||
if @props.tokenizedLine.text.length is 0
|
||||
@buildEmptyLineHTML()
|
||||
else
|
||||
@buildScopeTreeHTML(@props.tokenizedLine.getScopeTree())
|
||||
|
||||
buildEmptyLineHTML: ->
|
||||
{showIndentGuide, tokenizedLine} = @props
|
||||
{indentLevel, tabLength} = tokenizedLine
|
||||
|
||||
if showIndentGuide and indentLevel > 0
|
||||
indentSpan = "<span class='indent-guide'>#{multiplyString(' ', tabLength)}</span>"
|
||||
multiplyString(indentSpan, indentLevel + 1)
|
||||
else
|
||||
" "
|
||||
|
||||
buildScopeTreeHTML: (scopeTree) ->
|
||||
if scopeTree.children?
|
||||
html = "<span class='#{scopeTree.scope.replace(/\./g, ' ')}'>"
|
||||
html += @buildScopeTreeHTML(child) for child in scopeTree.children
|
||||
html += "</span>"
|
||||
html
|
||||
else
|
||||
"<span>#{scopeTree.getValueAsHtml({hasIndentGuide: @props.showIndentGuide})}</span>"
|
||||
|
||||
shouldComponentUpdate: (newProps) ->
|
||||
not isEqualForProperties(newProps, @props, 'showIndentGuide', 'lineHeight', 'screenRow')
|
||||
@@ -78,7 +78,7 @@ class MenuManager
|
||||
keystrokesByCommand = {}
|
||||
for binding in atom.keymaps.getKeyBindings() when @includeSelector(binding.selector)
|
||||
keystrokesByCommand[binding.command] ?= []
|
||||
keystrokesByCommand[binding.command].unshift binding.keystroke
|
||||
keystrokesByCommand[binding.command].unshift binding.keystrokes
|
||||
@sendToBrowserProcess(@template, keystrokesByCommand)
|
||||
|
||||
loadPlatformItems: ->
|
||||
|
||||
@@ -7,7 +7,7 @@ fs = require 'fs-plus'
|
||||
{Emitter} = require 'emissary'
|
||||
Q = require 'q'
|
||||
|
||||
{$} = require './space-pen-extensions'
|
||||
$ = null # Defer require in case this is in the window-less browser process
|
||||
ScopedProperties = require './scoped-properties'
|
||||
|
||||
# Loads and activates a package's main module and resources such as
|
||||
@@ -286,9 +286,11 @@ class Package
|
||||
handleActivationEvent: (event) =>
|
||||
bubblePathEventHandlers = @disableEventHandlersOnBubblePath(event)
|
||||
@activateNow()
|
||||
$ ?= require('./space-pen-extensions').$
|
||||
$(event.target).trigger(event)
|
||||
@restoreEventHandlersOnBubblePath(bubblePathEventHandlers)
|
||||
@unsubscribeFromActivationEvents()
|
||||
false
|
||||
|
||||
unsubscribeFromActivationEvents: ->
|
||||
return unless atom.workspaceView?
|
||||
@@ -303,6 +305,7 @@ class Package
|
||||
disableEventHandlersOnBubblePath: (event) ->
|
||||
bubblePathEventHandlers = []
|
||||
disabledHandler = ->
|
||||
$ ?= require('./space-pen-extensions').$
|
||||
element = $(event.target)
|
||||
while element.length
|
||||
if eventHandlers = element.handlers()?[event.type]
|
||||
|
||||
@@ -55,9 +55,9 @@ class PaneContainerView extends View
|
||||
|
||||
confirmClose: ->
|
||||
saved = true
|
||||
for pane in @getPaneViews()
|
||||
for item in pane.getItems()
|
||||
if not pane.promptToSaveItem(item)
|
||||
for paneView in @getPaneViews()
|
||||
for item in paneView.getItems()
|
||||
if not paneView.promptToSaveItem(item)
|
||||
saved = false
|
||||
break
|
||||
saved
|
||||
@@ -65,29 +65,33 @@ class PaneContainerView extends View
|
||||
getPaneViews: ->
|
||||
@find('.pane').views()
|
||||
|
||||
indexOfPane: (pane) ->
|
||||
@getPaneViews().indexOf(pane.view())
|
||||
indexOfPane: (paneView) ->
|
||||
@getPaneViews().indexOf(paneView.view())
|
||||
|
||||
paneAtIndex: (index) ->
|
||||
@getPaneViews()[index]
|
||||
|
||||
eachPaneView: (callback) ->
|
||||
callback(pane) for pane in @getPaneViews()
|
||||
paneAttached = (e) -> callback($(e.target).view())
|
||||
@on 'pane:attached', paneAttached
|
||||
off: => @off 'pane:attached', paneAttached
|
||||
callback(paneView) for paneView in @getPaneViews()
|
||||
paneViewAttached = (e) -> callback($(e.target).view())
|
||||
@on 'pane:attached', paneViewAttached
|
||||
off: => @off 'pane:attached', paneViewAttached
|
||||
|
||||
getFocusedPane: ->
|
||||
@find('.pane:has(:focus)').view()
|
||||
|
||||
getActivePane: ->
|
||||
deprecate("Use PaneContainerView::getActivePaneView instead.")
|
||||
@getActivePaneView()
|
||||
|
||||
getActivePaneView: ->
|
||||
@viewForModel(@model.activePane)
|
||||
|
||||
getActivePaneItem: ->
|
||||
@model.activePaneItem
|
||||
|
||||
getActiveView: ->
|
||||
@getActivePane()?.activeView
|
||||
@getActivePaneView()?.activeView
|
||||
|
||||
paneForUri: (uri) ->
|
||||
@viewForModel(@model.paneForUri(uri))
|
||||
@@ -116,29 +120,29 @@ class PaneContainerView extends View
|
||||
y = pointB.y - pointA.y
|
||||
Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))
|
||||
|
||||
pane = @getActivePane()
|
||||
box = @boundingBoxForPane(pane)
|
||||
panes = @getPaneViews()
|
||||
.filter (otherPane) =>
|
||||
otherBox = @boundingBoxForPane(otherPane)
|
||||
paneView = @getActivePaneView()
|
||||
box = @boundingBoxForPaneView(paneView)
|
||||
paneViews = @getPaneViews()
|
||||
.filter (otherPaneView) =>
|
||||
otherBox = @boundingBoxForPaneView(otherPaneView)
|
||||
switch direction
|
||||
when 'left' then otherBox.right.x <= box.left.x
|
||||
when 'right' then otherBox.left.x >= box.right.x
|
||||
when 'above' then otherBox.bottom.y <= box.top.y
|
||||
when 'below' then otherBox.top.y >= box.bottom.y
|
||||
.sort (paneA, paneB) =>
|
||||
boxA = @boundingBoxForPane(paneA)
|
||||
boxB = @boundingBoxForPane(paneB)
|
||||
.sort (paneViewA, paneViewB) =>
|
||||
boxA = @boundingBoxForPaneView(paneViewA)
|
||||
boxB = @boundingBoxForPaneView(paneViewB)
|
||||
switch direction
|
||||
when 'left' then distance(box.left, boxA.right) - distance(box.left, boxB.right)
|
||||
when 'right' then distance(box.right, boxA.left) - distance(box.right, boxB.left)
|
||||
when 'above' then distance(box.top, boxA.bottom) - distance(box.top, boxB.bottom)
|
||||
when 'below' then distance(box.bottom, boxA.top) - distance(box.bottom, boxB.top)
|
||||
|
||||
panes[0]
|
||||
paneViews[0]
|
||||
|
||||
boundingBoxForPane: (pane) ->
|
||||
boundingBox = pane[0].getBoundingClientRect()
|
||||
boundingBoxForPaneView: (paneView) ->
|
||||
boundingBox = paneView[0].getBoundingClientRect()
|
||||
|
||||
left: {x: boundingBox.left, y: boundingBox.top}
|
||||
right: {x: boundingBox.right, y: boundingBox.top}
|
||||
|
||||
@@ -39,6 +39,9 @@ class PaneContainer extends Model
|
||||
getPanes: ->
|
||||
@root?.getPanes() ? []
|
||||
|
||||
getActivePane: ->
|
||||
@activePane
|
||||
|
||||
paneForUri: (uri) ->
|
||||
find @getPanes(), (pane) -> pane.itemForUri(uri)?
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
{View, $} = require 'space-pen'
|
||||
React = require 'react'
|
||||
EditorComponent = require './editor-component'
|
||||
|
||||
module.exports =
|
||||
class ReactEditorView extends View
|
||||
@content: -> @div class: 'editor react-wrapper'
|
||||
|
||||
focusOnAttach: false
|
||||
|
||||
constructor: (@editor) ->
|
||||
super
|
||||
|
||||
getEditor: -> @editor
|
||||
|
||||
Object.defineProperty @::, 'lineHeight', get: -> @editor.getLineHeight()
|
||||
Object.defineProperty @::, 'charWidth', get: -> @editor.getDefaultCharWidth()
|
||||
|
||||
scrollTop: (scrollTop) ->
|
||||
if scrollTop?
|
||||
@editor.setScrollTop(scrollTop)
|
||||
else
|
||||
@editor.getScrollTop()
|
||||
|
||||
scrollLeft: (scrollLeft) ->
|
||||
if scrollLeft?
|
||||
@editor.setScrollLeft(scrollLeft)
|
||||
else
|
||||
@editor.getScrollLeft()
|
||||
|
||||
scrollToScreenPosition: (screenPosition) ->
|
||||
@editor.scrollToScreenPosition(screenPosition)
|
||||
|
||||
scrollToBufferPosition: (bufferPosition) ->
|
||||
@editor.scrollToBufferPosition(bufferPosition)
|
||||
|
||||
afterAttach: (onDom) ->
|
||||
return unless onDom
|
||||
@attached = true
|
||||
@component = React.renderComponent(EditorComponent({@editor, parentView: this}), @element)
|
||||
|
||||
node = @component.getDOMNode()
|
||||
|
||||
@underlayer = $(node).find('.underlayer')
|
||||
|
||||
@gutter = $(node).find('.gutter')
|
||||
@gutter.removeClassFromAllLines = (klass) =>
|
||||
@gutter.find('.line-number').removeClass(klass)
|
||||
|
||||
@gutter.addClassToLine = (bufferRow, klass) =>
|
||||
lines = @gutter.find(".line-number-#{bufferRow}")
|
||||
lines.addClass(klass)
|
||||
lines.length > 0
|
||||
|
||||
@focus() if @focusOnAttach
|
||||
|
||||
@trigger 'editor:attached', [this]
|
||||
|
||||
pixelPositionForBufferPosition: (bufferPosition) ->
|
||||
@editor.pixelPositionForBufferPosition(bufferPosition)
|
||||
|
||||
pixelPositionForScreenPosition: (screenPosition) ->
|
||||
@editor.pixelPositionForScreenPosition(screenPosition)
|
||||
|
||||
appendToLinesView: (view) ->
|
||||
view.css('position', 'absolute')
|
||||
@find('.scroll-view-content').prepend(view)
|
||||
|
||||
beforeRemove: ->
|
||||
React.unmountComponentAtNode(@element)
|
||||
@attached = false
|
||||
@trigger 'editor:detached', this
|
||||
|
||||
getPane: ->
|
||||
@closest('.pane').view()
|
||||
|
||||
focus: ->
|
||||
if @component?
|
||||
@component.onFocus()
|
||||
else
|
||||
@focusOnAttach = true
|
||||
@@ -0,0 +1,54 @@
|
||||
React = require 'react'
|
||||
{div} = require 'reactionary'
|
||||
{isEqualForProperties} = require 'underscore-plus'
|
||||
|
||||
module.exports =
|
||||
ScrollbarComponent = React.createClass
|
||||
render: ->
|
||||
{orientation, className, scrollHeight, scrollWidth} = @props
|
||||
|
||||
div {className, @onScroll},
|
||||
switch orientation
|
||||
when 'vertical'
|
||||
div className: 'scrollbar-content', style: {height: scrollHeight}
|
||||
when 'horizontal'
|
||||
div className: 'scrollbar-content', style: {width: scrollWidth}
|
||||
|
||||
componentDidMount: ->
|
||||
{orientation} = @props
|
||||
|
||||
unless orientation is 'vertical' or orientation is 'horizontal'
|
||||
throw new Error("Must specify an orientation property of 'vertical' or 'horizontal'")
|
||||
|
||||
shouldComponentUpdate: (newProps) ->
|
||||
switch @props.orientation
|
||||
when 'vertical'
|
||||
not isEqualForProperties(newProps, @props, 'scrollHeight', 'scrollTop')
|
||||
when 'horizontal'
|
||||
not isEqualForProperties(newProps, @props, 'scrollWidth', 'scrollLeft')
|
||||
|
||||
componentDidUpdate: ->
|
||||
{orientation, scrollTop, scrollLeft} = @props
|
||||
node = @getDOMNode()
|
||||
|
||||
switch orientation
|
||||
when 'vertical'
|
||||
node.scrollTop = scrollTop
|
||||
@props.scrollTop = node.scrollTop # Ensure scrollTop reflects actual DOM without triggering another update
|
||||
when 'horizontal'
|
||||
node.scrollLeft = scrollLeft
|
||||
@props.scrollLeft = node.scrollLeft # Ensure scrollLeft reflects actual DOM without triggering another update
|
||||
|
||||
onScroll: ->
|
||||
{orientation, onScroll} = @props
|
||||
node = @getDOMNode()
|
||||
|
||||
switch orientation
|
||||
when 'vertical'
|
||||
scrollTop = node.scrollTop
|
||||
@props.scrollTop = scrollTop # Ensure scrollTop reflects actual DOM without triggering another update
|
||||
onScroll(scrollTop)
|
||||
when 'horizontal'
|
||||
scrollLeft = node.scrollLeft
|
||||
@props.scrollLeft = scrollLeft # Ensure scrollLeft reflects actual DOM without triggering another update
|
||||
onScroll(scrollLeft)
|
||||
@@ -0,0 +1,11 @@
|
||||
React = require 'react'
|
||||
{div} = require 'reactionary'
|
||||
|
||||
module.exports =
|
||||
SelectionComponent = React.createClass
|
||||
displayName: 'SelectionComponent'
|
||||
|
||||
render: ->
|
||||
div className: 'selection',
|
||||
for regionRect, i in @props.selection.getRegionRects()
|
||||
div className: 'region', key: i, style: regionRect
|
||||
+53
-8
@@ -1,12 +1,10 @@
|
||||
{Point, Range} = require 'text-buffer'
|
||||
{Emitter} = require 'emissary'
|
||||
{Model} = require 'theorist'
|
||||
{pick} = require 'underscore-plus'
|
||||
|
||||
# Public: Represents a selection in the {Editor}.
|
||||
module.exports =
|
||||
class Selection
|
||||
Emitter.includeInto(this)
|
||||
|
||||
class Selection extends Model
|
||||
cursor: null
|
||||
marker: null
|
||||
editor: null
|
||||
@@ -14,7 +12,8 @@ class Selection
|
||||
wordwise: false
|
||||
needsAutoscroll: null
|
||||
|
||||
constructor: ({@cursor, @marker, @editor}) ->
|
||||
constructor: ({@cursor, @marker, @editor, id}) ->
|
||||
@assignId(id)
|
||||
@cursor.selection = this
|
||||
@marker.on 'changed', => @screenRangeChanged()
|
||||
@marker.on 'destroyed', =>
|
||||
@@ -74,11 +73,12 @@ class Selection
|
||||
setBufferRange: (bufferRange, options={}) ->
|
||||
bufferRange = Range.fromObject(bufferRange)
|
||||
@needsAutoscroll = options.autoscroll
|
||||
options.isReversed ?= @isReversed()
|
||||
options.reversed ?= @isReversed()
|
||||
@editor.destroyFoldsIntersectingBufferRange(bufferRange) unless options.preserveFolds
|
||||
@modifySelection =>
|
||||
@cursor.needsAutoscroll = false if options.autoscroll?
|
||||
@cursor.needsAutoscroll = false if @needsAutoscroll?
|
||||
@marker.setBufferRange(bufferRange, options)
|
||||
@autoscroll() if @needsAutoscroll and @editor.manageScrollPosition
|
||||
|
||||
# Public: Returns the starting and ending buffer rows the selection is
|
||||
# highlighting.
|
||||
@@ -91,6 +91,9 @@ class Selection
|
||||
end = Math.max(start, end - 1) if range.end.column == 0
|
||||
[start, end]
|
||||
|
||||
autoscroll: ->
|
||||
@editor.scrollToScreenRange(@getScreenRange())
|
||||
|
||||
# Public: Returns the text in the selection.
|
||||
getText: ->
|
||||
@editor.buffer.getTextInRange(@getBufferRange())
|
||||
@@ -293,7 +296,7 @@ class Selection
|
||||
if options.indentBasis? and not options.autoIndent
|
||||
text = @normalizeIndents(text, options.indentBasis)
|
||||
|
||||
newBufferRange = @editor.buffer.change(oldBufferRange, text, pick(options, 'undo'))
|
||||
newBufferRange = @editor.buffer.setTextInRange(oldBufferRange, text, pick(options, 'undo'))
|
||||
if options.select
|
||||
@setBufferRange(newBufferRange, isReversed: wasReversed)
|
||||
else
|
||||
@@ -570,6 +573,48 @@ class Selection
|
||||
compare: (otherSelection) ->
|
||||
@getBufferRange().compare(otherSelection.getBufferRange())
|
||||
|
||||
# Get the pixel dimensions of rectangular regions that cover selection's area
|
||||
# on the screen. Used by SelectionComponent for rendering.
|
||||
getRegionRects: ->
|
||||
lineHeight = @editor.getLineHeight()
|
||||
{start, end} = @getScreenRange()
|
||||
rowCount = end.row - start.row + 1
|
||||
startPixelPosition = @editor.pixelPositionForScreenPosition(start)
|
||||
endPixelPosition = @editor.pixelPositionForScreenPosition(end)
|
||||
|
||||
if rowCount is 1
|
||||
# Single line selection
|
||||
rects = [{
|
||||
top: startPixelPosition.top
|
||||
height: lineHeight
|
||||
left: startPixelPosition.left
|
||||
width: endPixelPosition.left - startPixelPosition.left
|
||||
}]
|
||||
else
|
||||
# Multi-line selection
|
||||
rects = []
|
||||
|
||||
# First row, extending from selection start to the right side of screen
|
||||
rects.push {
|
||||
top: startPixelPosition.top
|
||||
left: startPixelPosition.left
|
||||
height: lineHeight
|
||||
right: 0
|
||||
}
|
||||
if rowCount > 2
|
||||
# Middle rows, extending from left side to right side of screen
|
||||
rects.push {
|
||||
top: startPixelPosition.top + lineHeight
|
||||
height: (rowCount - 2) * lineHeight
|
||||
left: 0
|
||||
right: 0
|
||||
}
|
||||
# Last row, extending from left side of screen to selection end
|
||||
rects.push {top: endPixelPosition.top, height: lineHeight, left: 0, width: endPixelPosition.left }
|
||||
|
||||
rects
|
||||
|
||||
screenRangeChanged: ->
|
||||
screenRange = @getScreenRange()
|
||||
@emit 'screen-range-changed', screenRange
|
||||
@editor.selectionScreenRangeChanged(this)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
React = require 'react'
|
||||
{div} = require 'reactionary'
|
||||
SelectionComponent = require './selection-component'
|
||||
|
||||
module.exports =
|
||||
SelectionsComponent = React.createClass
|
||||
displayName: 'SelectionsComponent'
|
||||
|
||||
render: ->
|
||||
{editor} = @props
|
||||
|
||||
div className: 'selections',
|
||||
if @isMounted()
|
||||
for selection in editor.getSelections()
|
||||
if not selection.isEmpty() and editor.selectionIntersectsVisibleRowRange(selection)
|
||||
SelectionComponent({key: selection.id, selection})
|
||||
@@ -26,7 +26,7 @@ humanizeKeystrokes = (keystroke) ->
|
||||
|
||||
getKeystroke = (bindings) ->
|
||||
if bindings?.length
|
||||
"<span class=\"keystroke\">#{humanizeKeystrokes(bindings[0].keystroke)}</span>"
|
||||
"<span class=\"keystroke\">#{humanizeKeystrokes(bindings[0].keystrokes)}</span>"
|
||||
else
|
||||
''
|
||||
|
||||
@@ -40,9 +40,9 @@ jQuery.fn.setTooltip = (tooltipOptions, {command, commandElement}={}) ->
|
||||
tooltipOptions = {title: tooltipOptions} if _.isString(tooltipOptions)
|
||||
|
||||
if commandElement
|
||||
bindings = atom.keymaps.keyBindingsForCommandMatchingElement(command, commandElement)
|
||||
bindings = atom.keymaps.findKeyBindings(command: command, target: commandElement[0])
|
||||
else if command
|
||||
bindings = atom.keymaps.keyBindingsForCommand(command)
|
||||
bindings = atom.keymaps.findKeyBindings(command: command)
|
||||
|
||||
tooltipOptions.title = "#{tooltipOptions.title} #{getKeystroke(bindings)}"
|
||||
|
||||
@@ -69,4 +69,6 @@ jQuery(document.body).on 'show.bs.tooltip', ({target}) ->
|
||||
jQuery.fn.setTooltip.getKeystroke = getKeystroke
|
||||
jQuery.fn.setTooltip.humanizeKeystrokes = humanizeKeystrokes
|
||||
|
||||
Object.defineProperty jQuery.fn, 'element', get: -> @[0]
|
||||
|
||||
module.exports = spacePen
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{Subscriber} = require 'emissary'
|
||||
SubscriberMixin = componentDidUnmount: -> @unsubscribe()
|
||||
Subscriber.extend(SubscriberMixin)
|
||||
module.exports = SubscriberMixin
|
||||
+2
-2
@@ -68,7 +68,7 @@ class Syntax extends GrammarRegistry
|
||||
getProperty: (scope, keyPath) ->
|
||||
scopeChain = scope
|
||||
.map (scope) ->
|
||||
scope = ".#{scope}" unless scope.indexOf('.') is 0
|
||||
scope = ".#{scope}" unless scope[0] is '.'
|
||||
scope
|
||||
.join(' ')
|
||||
@propertyStore.getPropertyValue(scopeChain, keyPath)
|
||||
@@ -76,7 +76,7 @@ class Syntax extends GrammarRegistry
|
||||
propertiesForScope: (scope, keyPath) ->
|
||||
scopeChain = scope
|
||||
.map (scope) ->
|
||||
scope = ".#{scope}" unless scope.indexOf('.') is 0
|
||||
scope = ".#{scope}" unless scope[0] is '.'
|
||||
scope
|
||||
.join(' ')
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ class ThemeManager
|
||||
getEnabledThemeNames: ->
|
||||
themeNames = atom.config.get('core.themes') ? []
|
||||
themeNames = [themeNames] unless _.isArray(themeNames)
|
||||
themeNames = themeNames.filter (themeName) ->
|
||||
themeName and typeof themeName is 'string'
|
||||
|
||||
# Reverse so the first (top) theme is loaded after the others. We want
|
||||
# the first/top theme to override later themes in the stack.
|
||||
|
||||
+16
-13
@@ -20,6 +20,8 @@ class Token
|
||||
scopes: null
|
||||
isAtomic: null
|
||||
isHardTab: null
|
||||
hasLeadingWhitespace: false
|
||||
hasTrailingWhitespace: false
|
||||
|
||||
constructor: ({@value, @scopes, @isAtomic, @bufferDelta, @isHardTab}) ->
|
||||
@screenDelta = @value.length
|
||||
@@ -40,7 +42,7 @@ class Token
|
||||
whitespaceRegexForTabLength: (tabLength) ->
|
||||
WhitespaceRegexesByTabLength[tabLength] ?= new RegExp("([ ]{#{tabLength}})|(\t)|([^\t]+)", "g")
|
||||
|
||||
breakOutAtomicTokens: (tabLength, breakOutLeadingWhitespace) ->
|
||||
breakOutAtomicTokens: (tabLength, breakOutLeadingSoftTabs) ->
|
||||
if @hasSurrogatePair
|
||||
outputTokens = []
|
||||
|
||||
@@ -48,14 +50,14 @@ class Token
|
||||
if token.isAtomic
|
||||
outputTokens.push(token)
|
||||
else
|
||||
outputTokens.push(token.breakOutAtomicTokens(tabLength, breakOutLeadingWhitespace)...)
|
||||
breakOutLeadingWhitespace = token.isOnlyWhitespace() if breakOutLeadingWhitespace
|
||||
outputTokens.push(token.breakOutAtomicTokens(tabLength, breakOutLeadingSoftTabs)...)
|
||||
breakOutLeadingSoftTabs = token.isOnlyWhitespace() if breakOutLeadingSoftTabs
|
||||
|
||||
outputTokens
|
||||
else
|
||||
return [this] if @isAtomic
|
||||
|
||||
if breakOutLeadingWhitespace
|
||||
if breakOutLeadingSoftTabs
|
||||
return [this] unless /^[ ]|\t/.test(@value)
|
||||
else
|
||||
return [this] unless /\t/.test(@value)
|
||||
@@ -64,13 +66,13 @@ class Token
|
||||
regex = @whitespaceRegexForTabLength(tabLength)
|
||||
while match = regex.exec(@value)
|
||||
[fullMatch, softTab, hardTab] = match
|
||||
if softTab and breakOutLeadingWhitespace
|
||||
outputTokens.push(@buildSoftTabToken(tabLength, false))
|
||||
if softTab and breakOutLeadingSoftTabs
|
||||
outputTokens.push(@buildSoftTabToken(tabLength))
|
||||
else if hardTab
|
||||
breakOutLeadingWhitespace = false
|
||||
outputTokens.push(@buildHardTabToken(tabLength, true))
|
||||
breakOutLeadingSoftTabs = false
|
||||
outputTokens.push(@buildHardTabToken(tabLength))
|
||||
else
|
||||
breakOutLeadingWhitespace = false
|
||||
breakOutLeadingSoftTabs = false
|
||||
value = match[0]
|
||||
outputTokens.push(new Token({value, @scopes}))
|
||||
|
||||
@@ -127,7 +129,7 @@ class Token
|
||||
scopeClasses = scope.split('.')
|
||||
_.isSubset(targetClasses, scopeClasses)
|
||||
|
||||
getValueAsHtml: ({invisibles, hasLeadingWhitespace, hasTrailingWhitespace, hasIndentGuide})->
|
||||
getValueAsHtml: ({invisibles, hasIndentGuide})->
|
||||
invisibles ?= {}
|
||||
if @isHardTab
|
||||
classes = 'hard-tab'
|
||||
@@ -142,7 +144,7 @@ class Token
|
||||
leadingHtml = ''
|
||||
trailingHtml = ''
|
||||
|
||||
if hasLeadingWhitespace and match = LeadingWhitespaceRegex.exec(@value)
|
||||
if @hasLeadingWhitespace and match = LeadingWhitespaceRegex.exec(@value)
|
||||
classes = 'leading-whitespace'
|
||||
classes += ' indent-guide' if hasIndentGuide
|
||||
classes += ' invisible-character' if invisibles.space
|
||||
@@ -152,9 +154,10 @@ class Token
|
||||
|
||||
startIndex = match[0].length
|
||||
|
||||
if hasTrailingWhitespace and match = TrailingWhitespaceRegex.exec(@value)
|
||||
if @hasTrailingWhitespace and match = TrailingWhitespaceRegex.exec(@value)
|
||||
tokenIsOnlyWhitespace = match[0].length is @value.length
|
||||
classes = 'trailing-whitespace'
|
||||
classes += ' indent-guide' if hasIndentGuide and not hasLeadingWhitespace
|
||||
classes += ' indent-guide' if hasIndentGuide and not @hasLeadingWhitespace and tokenIsOnlyWhitespace
|
||||
classes += ' invisible-character' if invisibles.space
|
||||
|
||||
match[0] = match[0].replace(CharacterRegex, invisibles.space) if invisibles.space
|
||||
|
||||
@@ -185,14 +185,16 @@ class TokenizedBuffer extends Model
|
||||
line = @buffer.lineForRow(row)
|
||||
tokens = [new Token(value: line, scopes: [@grammar.scopeName])]
|
||||
tabLength = @getTabLength()
|
||||
new TokenizedLine({tokens, tabLength})
|
||||
indentLevel = @indentLevelForRow(row)
|
||||
new TokenizedLine({tokens, tabLength, indentLevel})
|
||||
|
||||
buildTokenizedTokenizedLineForRow: (row, ruleStack) ->
|
||||
line = @buffer.lineForRow(row)
|
||||
lineEnding = @buffer.lineEndingForRow(row)
|
||||
tabLength = @getTabLength()
|
||||
indentLevel = @indentLevelForRow(row)
|
||||
{ tokens, ruleStack } = @grammar.tokenizeLine(line, ruleStack, row is 0)
|
||||
new TokenizedLine({tokens, ruleStack, tabLength, lineEnding})
|
||||
new TokenizedLine({tokens, ruleStack, tabLength, lineEnding, indentLevel})
|
||||
|
||||
# FIXME: benogle says: These are actually buffer rows as all buffer rows are
|
||||
# accounted for in @tokenizedLines
|
||||
@@ -207,6 +209,36 @@ class TokenizedBuffer extends Model
|
||||
stackForRow: (row) ->
|
||||
@tokenizedLines[row]?.ruleStack
|
||||
|
||||
indentLevelForRow: (row) ->
|
||||
line = @buffer.lineForRow(row)
|
||||
|
||||
if line is ''
|
||||
nextRow = row + 1
|
||||
lineCount = @getLineCount()
|
||||
while nextRow < lineCount
|
||||
nextLine = @buffer.lineForRow(nextRow)
|
||||
return @indentLevelForLine(nextLine) unless nextLine is ''
|
||||
nextRow++
|
||||
|
||||
previousRow = row - 1
|
||||
while previousRow >= 0
|
||||
previousLine = @buffer.lineForRow(previousRow)
|
||||
return @indentLevelForLine(previousLine) unless previousLine is ''
|
||||
previousRow--
|
||||
|
||||
0
|
||||
else
|
||||
@indentLevelForLine(line)
|
||||
|
||||
indentLevelForLine: (line) ->
|
||||
if match = line.match(/^[\t ]+/)
|
||||
leadingWhitespace = match[0]
|
||||
tabCount = leadingWhitespace.match(/\t/g)?.length ? 0
|
||||
spaceCount = leadingWhitespace.match(/[ ]/g)?.length ? 0
|
||||
tabCount + (spaceCount / @getTabLength())
|
||||
else
|
||||
0
|
||||
|
||||
scopesForPosition: (position) ->
|
||||
@tokenForPosition(position).scopes
|
||||
|
||||
@@ -306,6 +338,9 @@ class TokenizedBuffer extends Model
|
||||
getLastRow: ->
|
||||
@buffer.getLastRow()
|
||||
|
||||
getLineCount: ->
|
||||
@buffer.getLineCount()
|
||||
|
||||
logLines: (start=0, end=@buffer.getLastRow()) ->
|
||||
for row in [start..end]
|
||||
line = @lineForScreenRow(row).text
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
idCounter = 1
|
||||
|
||||
module.exports =
|
||||
class TokenizedLine
|
||||
constructor: ({tokens, @lineEnding, @ruleStack, @startBufferColumn, @fold, tabLength}) ->
|
||||
@tokens = @breakOutAtomicTokens(tokens, tabLength)
|
||||
constructor: ({tokens, @lineEnding, @ruleStack, @startBufferColumn, @fold, @tabLength, @indentLevel}) ->
|
||||
@tokens = @breakOutAtomicTokens(tokens)
|
||||
@startBufferColumn ?= 0
|
||||
@text = _.pluck(@tokens, 'value').join('')
|
||||
@bufferDelta = _.sum(_.pluck(@tokens, 'bufferDelta'))
|
||||
@id = idCounter++
|
||||
@markLeadingAndTrailingWhitespaceTokens()
|
||||
|
||||
copy: ->
|
||||
new TokenizedLine({@tokens, @lineEnding, @ruleStack, @startBufferColumn, @fold})
|
||||
@@ -106,14 +110,25 @@ class TokenizedLine
|
||||
delta = nextDelta
|
||||
delta
|
||||
|
||||
breakOutAtomicTokens: (inputTokens, tabLength) ->
|
||||
breakOutAtomicTokens: (inputTokens) ->
|
||||
outputTokens = []
|
||||
breakOutLeadingWhitespace = true
|
||||
breakOutLeadingSoftTabs = true
|
||||
for token in inputTokens
|
||||
outputTokens.push(token.breakOutAtomicTokens(tabLength, breakOutLeadingWhitespace)...)
|
||||
breakOutLeadingWhitespace = token.isOnlyWhitespace() if breakOutLeadingWhitespace
|
||||
outputTokens.push(token.breakOutAtomicTokens(@tabLength, breakOutLeadingSoftTabs)...)
|
||||
breakOutLeadingSoftTabs = token.isOnlyWhitespace() if breakOutLeadingSoftTabs
|
||||
outputTokens
|
||||
|
||||
markLeadingAndTrailingWhitespaceTokens: ->
|
||||
firstNonWhitespacePosition = @text.search(/\S/)
|
||||
firstTrailingWhitespacePosition = @text.search(/\s*$/)
|
||||
lineIsWhitespaceOnly = firstTrailingWhitespacePosition is 0
|
||||
position = 0
|
||||
for token, i in @tokens
|
||||
token.hasLeadingWhitespace = position < firstNonWhitespacePosition
|
||||
# Only the *last* segment of a soft-wrapped line can have trailing whitespace
|
||||
token.hasTrailingWhitespace = @lineEnding? and (position + token.value.length > firstTrailingWhitespacePosition)
|
||||
position += token.value.length
|
||||
|
||||
isComment: ->
|
||||
for token in @tokens
|
||||
continue if token.scopes.length is 1
|
||||
@@ -134,3 +149,33 @@ class TokenizedLine
|
||||
for token in @tokens
|
||||
return column if token is targetToken
|
||||
column += token.bufferDelta
|
||||
|
||||
getScopeTree: ->
|
||||
return @scopeTree if @scopeTree?
|
||||
|
||||
scopeStack = []
|
||||
for token in @tokens
|
||||
@updateScopeStack(scopeStack, token.scopes)
|
||||
_.last(scopeStack).children.push(token)
|
||||
|
||||
@scopeTree = scopeStack[0]
|
||||
@updateScopeStack(scopeStack, [])
|
||||
@scopeTree
|
||||
|
||||
updateScopeStack: (scopeStack, desiredScopes) ->
|
||||
# Find a common prefix
|
||||
for scope, i in desiredScopes
|
||||
break unless scopeStack[i]?.scope is desiredScopes[i]
|
||||
|
||||
# Pop scopes until we're at the common prefx
|
||||
until scopeStack.length is i
|
||||
poppedScope = scopeStack.pop()
|
||||
_.last(scopeStack)?.children.push(poppedScope)
|
||||
|
||||
# Push onto common prefix until scopeStack equals desiredScopes
|
||||
for j in [i...desiredScopes.length]
|
||||
scopeStack.push(new Scope(desiredScopes[j]))
|
||||
|
||||
class Scope
|
||||
constructor: (@scope) ->
|
||||
@children = []
|
||||
|
||||
@@ -58,7 +58,7 @@ class WindowEventHandler
|
||||
@subscribeToCommand $(document), 'core:focus-previous', @focusPrevious
|
||||
|
||||
@subscribe $(document), 'keydown', (event) ->
|
||||
atom.keymaps.handleKeyEvent(event)
|
||||
atom.keymaps.handleKeyboardEvent(event.originalEvent)
|
||||
|
||||
@subscribe $(document), 'drop', (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
@@ -71,6 +71,7 @@ class WorkspaceView extends View
|
||||
projectHome: path.join(fs.getHomeDirectory(), 'github')
|
||||
audioBeep: true
|
||||
destroyEmptyPanes: true
|
||||
useReactEditor: false
|
||||
|
||||
@content: ->
|
||||
@div class: 'workspace', tabindex: -1, =>
|
||||
@@ -151,7 +152,7 @@ class WorkspaceView extends View
|
||||
|
||||
@command 'pane:reopen-closed-item', => @reopenItemSync()
|
||||
|
||||
@command 'core:close', => if @getActivePaneItem()? then @destroyActivePaneItem() else @destroyActivePane()
|
||||
@command 'core:close', => if @getModel().getActivePaneItem()? then @destroyActivePaneItem() else @destroyActivePane()
|
||||
@command 'core:save', => @saveActivePaneItem()
|
||||
@command 'core:save-as', => @saveActivePaneItemAs()
|
||||
|
||||
@@ -182,8 +183,8 @@ class WorkspaceView extends View
|
||||
detailedMessage: "The shell commands `atom` and `apm` are installed."
|
||||
|
||||
handleFocus: ->
|
||||
if @getActivePane()
|
||||
@getActivePane().focus()
|
||||
if @getActivePaneView()
|
||||
@getActivePaneView().focus()
|
||||
false
|
||||
else
|
||||
@updateTitle()
|
||||
@@ -205,7 +206,7 @@ class WorkspaceView extends View
|
||||
# Updates the application's title, based on whichever file is open.
|
||||
updateTitle: ->
|
||||
if projectPath = atom.project.getPath()
|
||||
if item = @getActivePaneItem()
|
||||
if item = @getModel().getActivePaneItem()
|
||||
@setTitle("#{item.getTitle?() ? 'untitled'} - #{projectPath}")
|
||||
else
|
||||
@setTitle(projectPath)
|
||||
@@ -272,7 +273,7 @@ class WorkspaceView extends View
|
||||
#
|
||||
# Returns a {PaneView}.
|
||||
getActivePaneView: ->
|
||||
@panes.getActivePane()
|
||||
@panes.getActivePaneView()
|
||||
|
||||
# Public: Get the view associated with the active pane item.
|
||||
#
|
||||
@@ -316,18 +317,20 @@ class WorkspaceView extends View
|
||||
#
|
||||
# Returns an Array of all open {PaneView}s.
|
||||
getPaneViews: ->
|
||||
@panes.getPanes()
|
||||
@panes.getPaneViews()
|
||||
|
||||
# Public: Register a function to be called for every current and future
|
||||
# editor view in the workspace.
|
||||
# editor view in the workspace (only includes {EditorView}s that are pane
|
||||
# items).
|
||||
#
|
||||
# callback - A {Function} with an {EditorView} as its only argument.
|
||||
#
|
||||
# Returns a subscription object with an `.off` method that you can call to
|
||||
# unregister the callback.
|
||||
eachEditorView: (callback) ->
|
||||
callback(editor) for editor in @getEditorViews()
|
||||
attachedCallback = (e, editor) -> callback(editor)
|
||||
callback(editorView) for editorView in @getEditorViews()
|
||||
attachedCallback = (e, editorView) ->
|
||||
callback(editorView) unless editorView.mini
|
||||
@on('editor:attached', attachedCallback)
|
||||
off: => @off('editor:attached', attachedCallback)
|
||||
|
||||
|
||||
@@ -223,6 +223,12 @@ class Workspace extends Model
|
||||
paneForUri: (uri) ->
|
||||
@paneContainer.paneForUri(uri)
|
||||
|
||||
# Public: Get the active {Pane}'s active item.
|
||||
#
|
||||
# Returns an pane item {Object}.
|
||||
getActivePaneItem: ->
|
||||
@paneContainer.getActivePane().getActiveItem()
|
||||
|
||||
# Public: Save the active pane item.
|
||||
#
|
||||
# If the active pane item currently has a URI according to the item's
|
||||
|
||||
+58
-1
@@ -2,6 +2,64 @@
|
||||
@import "octicon-utf-codes";
|
||||
@import "octicon-mixins";
|
||||
|
||||
.editor.react {
|
||||
.underlayer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: -2;
|
||||
}
|
||||
|
||||
.lines {
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.horizontal-scrollbar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
height: 15px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
z-index: 3;
|
||||
|
||||
.scrollbar-content {
|
||||
height: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-view {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.scroll-view-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gutter {
|
||||
padding-left: 0.5em;
|
||||
padding-right: 0.5em;
|
||||
|
||||
.line-number {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
|
||||
.icon-right {
|
||||
padding: 0;
|
||||
padding-left: .1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editor {
|
||||
overflow: hidden;
|
||||
cursor: text;
|
||||
@@ -28,7 +86,6 @@
|
||||
.editor .gutter .line-number {
|
||||
padding-left: .5em;
|
||||
opacity: 0.6;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.editor .gutter .line-numbers {
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@
|
||||
background-color: @pane-item-background-color;
|
||||
}
|
||||
|
||||
> * {
|
||||
> *, > .react-wrapper > * {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
||||
externo
+1
-1
Submodule vendor/apm updated: 2ce1206e67...d349e45263
Referência em uma Nova Issue
Bloquear um usuário