mirror of
https://github.com/atom/atom.git
synced 2026-01-23 22:08:08 -05:00
Merge branch 'master' into mb-synchronous-scroll-position
Conflicts: src/text-editor-presenter.coffee src/text-editor.coffee
This commit is contained in:
@@ -1 +1,6 @@
|
||||
See https://atom.io/releases
|
||||
|
||||
## 1.3.0
|
||||
|
||||
* The tree-view now sorts directory entries more naturally, in a locale-sensitive way.
|
||||
* Lines can now be moved up and down with multiple cursors.
|
||||
|
||||
107
CONTRIBUTING.md
107
CONTRIBUTING.md
@@ -12,7 +12,7 @@ These are just guidelines, not rules, use your best judgment and feel free to pr
|
||||
* [Atom and Packages](#atom-and-packages)
|
||||
|
||||
[How Can I Contribute?](#how-can-i-contribute)
|
||||
* [Submitting Issues](#submitting-issues)
|
||||
* [Reporting Bugs](#reporting-bugs)
|
||||
* [Your First Code Contribution](#your-first-code-contribution)
|
||||
* [Pull Requests](#pull-requests)
|
||||
|
||||
@@ -71,30 +71,91 @@ For more information on how to work with Atom's official packages, see [Contribu
|
||||
Also, because Atom is so extensible, it's possible that a feature you've become accustomed to in Atom or an issue you're encountering aren't coming from a bundled package at all, but rather a [community package](https://atom.io/packages) you've installed.
|
||||
Each community package has its own repository too, and you should be able to find it in Settings > Packages for the packages you installed and contribute there.
|
||||
|
||||
## How can I contribute?
|
||||
## How Can I Contribute?
|
||||
|
||||
### Submitting Issues
|
||||
### Reporting Bugs
|
||||
|
||||
* You can create an issue [here](https://github.com/atom/atom/issues/new), but
|
||||
before doing that please read the notes below on debugging and submitting issues,
|
||||
and include as many details as possible with your report.
|
||||
* Check the [debugging guide](https://atom.io/docs/latest/hacking-atom-debugging) for tips
|
||||
on debugging. You might be able to find the cause of the problem and fix
|
||||
things yourself.
|
||||
* Include the version of Atom you are using and the OS.
|
||||
* Include screenshots and animated GIFs whenever possible; they are immensely
|
||||
helpful.
|
||||
* Include the behavior you expected and other places you've seen that behavior
|
||||
such as Emacs, vi, Xcode, etc.
|
||||
* Check the dev tools (`alt-cmd-i`) for errors to include. If the dev tools
|
||||
are open _before_ the error is triggered, a full stack trace for the error
|
||||
will be logged. If you can reproduce the error, use this approach to get the
|
||||
full stack trace and include it in the issue.
|
||||
* On Mac, check Console.app for stack traces to include if reporting a crash.
|
||||
* Perform a [cursory search](https://github.com/issues?q=+is%3Aissue+user%3Aatom)
|
||||
to see if a similar issue has already been submitted.
|
||||
* Please setup a [profile picture](https://help.github.com/articles/how-do-i-set-up-my-profile-picture)
|
||||
to make yourself recognizable and so we can all get to know each other better.
|
||||
This section guides you through submitting a bug report for Atom. Following these guidelines helps maintainers and the community understand your report :pencil:, reproduce the behavior :computer: :computer:, and find related reports :mag_right:.
|
||||
|
||||
Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). If you'd like, you can use [this template](#template-for-submitting-bug-reports) to structure the information.
|
||||
|
||||
#### Before Submitting A Bug Report
|
||||
|
||||
* **Check the [debugging guide](https://atom.io/docs/latest/hacking-atom-debugging).** You might be able to find the cause of the problem and fix things yourself. Most importantly, check if you can reproduce the problem [in the latest version of Atom](https://atom.io/docs/latest/hacking-atom-debugging#update-to-the-latest-version), if the problem happens when you run Atom in [safe mode](https://atom.io/docs/latest/hacking-atom-debugging#check-if-the-problem-shows-up-in-safe-mode), and if you can get the desired behavior by changing [Atom's or packages' config settings](https://atom.io/docs/latest/hacking-atom-debugging#check-atom-and-package-settings).
|
||||
* **Check the [FAQs on the forum](https://discuss.atom.io/c/faq)** for a list of common questions and problems.
|
||||
* **Determine [which repository the problem should be reported in](#atom-and-packages)**.
|
||||
* **Perform a [cursory search](https://github.com/issues?q=+is%3Aissue+user%3Aatom)** to see if the problem has already been reported. If it has, add a comment to the existing issue instead of opening a new one.
|
||||
|
||||
#### How Do I Submit A (Good) Bug Report?
|
||||
|
||||
Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you've determined [which repository](#atom-and-packages) your bug is related to, create an issue on that repository and provide the following information.
|
||||
|
||||
Explain the problem and include additional details to help maintainers reproduce the problem:
|
||||
|
||||
* **Use a clear and descriptive title** for the issue to identify the problem.
|
||||
* **Describe the exact steps which reproduce the problem** in as many details as possible. For example, start by explaining how you started Atom, e.g. which command exactly you used in the terminal, or how you started Atom otherwise. When listing steps, **don't just say what you did, but explain how you did it**. For example, if you moved the cursor to the end of a line, explain if you used the mouse, or a keyboard shortcut or an Atom command, and if so which one?
|
||||
* **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
|
||||
* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
|
||||
* **Explain which behavior you expected to see instead and why.**
|
||||
* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. If you use the keyboard while following the steps, **record the GIF with the [Keybinding Resolver](https://github.com/atom/keybinding-resolver) shown**. You can use [this tool](http://www.cockos.com/licecap/) to record GIFs on OSX and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
|
||||
* **If you're reporting that Atom crashed**, include a crash report with a stack trace from the operating system. On OSX, the crash report will be available in `Console.app` under "Diagnostic and usage information" > "User diagnostic reports". Include the crash report in the issue in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines) or put it in a [gist](https://gist.github.com/) and provide link to that gist.
|
||||
* **If the problem is related to performance**, include a [CPU profile capture and a screenshot](https://atom.io/docs/latest/hacking-atom-debugging#diagnose-performance-problems-with-the-dev-tools-cpu-profiler) with your report.
|
||||
* **If the Chrome's developer tools pane is shown without you triggering it**, that normally means that an exception was thrown. The Console tab will include an entry for the exception. Expand the exception so that the stack trace is visible, and provide the full exception and stack trace in a [code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines) and as a screenshot.
|
||||
* **If the problem wasn't triggered by a specific action**, describe what you were doing before the problem happened and share more information using the guidelines below.
|
||||
|
||||
Provide more context by answering these questions:
|
||||
|
||||
* **Can you reproduce the problem in [safe mode](https://atom.io/docs/latest/hacking-atom-debugging#check-if-the-problem-shows-up-in-safe-mode)?**
|
||||
* **Did the problem start happening recently** (e.g. after updating to a new version of Atom) or was this always a problem?
|
||||
* If the problem started happening recently, **can you reproduce the problem in an older version of Atom?** What's the most recent version in which the problem doesn't happen? You can download older versions of Atom from [the releases page](https://github.com/atom/atom/releases).
|
||||
* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens.
|
||||
* If the problem is related to working with files (e.g. opening and editing files), **does the problem happen for all files and projects or only some?** Does the problem happen only when working with local or remote files (e.g. on network drives), with files of a specific type (e.g. only JavaScript or Python files), with large files or files with very long lines, or with files in a specific encoding? Is there anything else special about the files you are using?
|
||||
|
||||
Include details about your configuration and environment:
|
||||
|
||||
* **Which version of Atom are you using?** You can get the exact version by running `atom -v` in your terminal, or by starting Atom and running the `Application: About` command from the [Command Palette](https://github.com/atom/command-palette).
|
||||
* **What's the name and version of the OS you're using**?
|
||||
* **Are you running Atom in a virtual machine?** If so, which VM software are you using and which operating systems and versions are used for the host and the guest?
|
||||
* **Which [packages](#atom-and-packages) do you have installed?** You can get that list by running `apm list --installed`.
|
||||
* **Are you using [local configuration files](https://atom.io/docs/latest/using-atom-basic-customization)** `config.cson`, `keymap.cson`, `snippets.cson`, `styles.less` and `init.coffee` to customize Atom? If so, provide the contents of those files, preferably in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines) or with a link to a [gist](https://gist.github.com/).
|
||||
* **Are you using Atom with multiple monitors?** If so, can you reproduce the problem when you use a single monitor?
|
||||
* **Which keyboard layout are you using?** Are you using a US layout or some other layout?
|
||||
|
||||
#### Template For Submitting Bug Reports
|
||||
|
||||
[Short description of problem here]
|
||||
|
||||
**Reproduction Steps:**
|
||||
|
||||
1. [First Step]
|
||||
2. [Second Step]
|
||||
3. [Other Steps...]
|
||||
|
||||
**Expected behavior:**
|
||||
|
||||
[Describe expected behavior here]
|
||||
|
||||
**Observed behavior:**
|
||||
|
||||
[Describe observed behavior here]
|
||||
|
||||
**Screenshots and GIFs**
|
||||
|
||||

|
||||
|
||||
**Atom version:** [Enter Atom version here]
|
||||
**OS and version:** [Enter OS name and version here]
|
||||
|
||||
**Installed packages:**
|
||||
|
||||
[List of installed packages here]
|
||||
|
||||
**Additional information:**
|
||||
|
||||
* Problem can be reproduced in safe mode: [Yes/No]
|
||||
* Problem started happening recently, didn't happen in an older version of Atom: [Yes/No]
|
||||
* Problem can be reliably reproduced, doesn't happen randomly: [Yes/No]
|
||||
* Problem happens with all files and projects, not only some files or projects: [Yes/No]
|
||||
|
||||
### Your First Code Contribution
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
{ type: 'separator' }
|
||||
{ label: 'Install Shell Commands', command: 'window:install-shell-commands' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Services', submenu: [] }
|
||||
{ label: 'Services', role: 'services', submenu: [] }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Hide Atom', command: 'application:hide' }
|
||||
{ label: 'Hide Others', command: 'application:hide-other-applications' }
|
||||
@@ -184,6 +184,7 @@
|
||||
|
||||
{
|
||||
label: 'Window'
|
||||
role: 'window'
|
||||
submenu: [
|
||||
{ label: 'Minimize', command: 'application:minimize' }
|
||||
{ label: 'Zoom', command: 'application:zoom' }
|
||||
@@ -194,6 +195,7 @@
|
||||
|
||||
{
|
||||
label: 'Help'
|
||||
role: 'help'
|
||||
submenu: [
|
||||
{ label: 'Terms of Use', command: 'application:open-terms-of-use' }
|
||||
{ label: 'Documentation', command: 'application:open-documentation' }
|
||||
|
||||
38
package.json
38
package.json
@@ -12,7 +12,7 @@
|
||||
"url": "https://github.com/atom/atom/issues"
|
||||
},
|
||||
"license": "MIT",
|
||||
"electronVersion": "0.34.0",
|
||||
"electronVersion": "0.34.3",
|
||||
"dependencies": {
|
||||
"async": "0.2.6",
|
||||
"atom-keymap": "^6.1.0",
|
||||
@@ -28,7 +28,7 @@
|
||||
"fs-plus": "^2.8.0",
|
||||
"fstream": "0.1.24",
|
||||
"fuzzaldrin": "^2.1",
|
||||
"git-utils": "^4",
|
||||
"git-utils": "^4.0.7",
|
||||
"grim": "1.5.0",
|
||||
"jasmine-json": "~0.0",
|
||||
"jasmine-tagged": "^1.1.4",
|
||||
@@ -39,7 +39,7 @@
|
||||
"normalize-package-data": "^2.0.0",
|
||||
"nslog": "^3",
|
||||
"oniguruma": "^5",
|
||||
"pathwatcher": "^6.2",
|
||||
"pathwatcher": "~6.2",
|
||||
"property-accessors": "^1.1.3",
|
||||
"random-words": "0.0.1",
|
||||
"resolve": "^1.1.6",
|
||||
@@ -52,7 +52,7 @@
|
||||
"service-hub": "^0.7.0",
|
||||
"source-map-support": "^0.3.2",
|
||||
"temp": "0.8.1",
|
||||
"text-buffer": "7.1.3",
|
||||
"text-buffer": "^8.0.3",
|
||||
"typescript-simple": "1.0.0",
|
||||
"underscore-plus": "^1.6.6",
|
||||
"yargs": "^3.23.0"
|
||||
@@ -76,7 +76,7 @@
|
||||
"autocomplete-css": "0.11.0",
|
||||
"autocomplete-html": "0.7.2",
|
||||
"autocomplete-plus": "2.23.0",
|
||||
"autocomplete-snippets": "1.7.1",
|
||||
"autocomplete-snippets": "1.8.0",
|
||||
"autoflow": "0.26.0",
|
||||
"autosave": "0.23.0",
|
||||
"background-tips": "0.26.0",
|
||||
@@ -87,31 +87,31 @@
|
||||
"dev-live-reload": "0.47.0",
|
||||
"encoding-selector": "0.21.0",
|
||||
"exception-reporting": "0.37.0",
|
||||
"find-and-replace": "0.190.0",
|
||||
"find-and-replace": "0.191.0",
|
||||
"fuzzy-finder": "0.93.0",
|
||||
"git-diff": "0.57.0",
|
||||
"go-to-line": "0.30.0",
|
||||
"grammar-selector": "0.48.0",
|
||||
"image-view": "0.55.0",
|
||||
"image-view": "0.56.0",
|
||||
"incompatible-packages": "0.25.0",
|
||||
"keybinding-resolver": "0.33.0",
|
||||
"line-ending-selector": "0.3.0",
|
||||
"link": "0.31.0",
|
||||
"markdown-preview": "0.156.0",
|
||||
"markdown-preview": "0.156.1",
|
||||
"metrics": "0.53.0",
|
||||
"notifications": "0.61.0",
|
||||
"open-on-github": "0.39.0",
|
||||
"open-on-github": "0.40.0",
|
||||
"package-generator": "0.41.0",
|
||||
"release-notes": "0.53.0",
|
||||
"settings-view": "0.231.0",
|
||||
"snippets": "0.101.0",
|
||||
"spell-check": "0.62.0",
|
||||
"settings-view": "0.232.0",
|
||||
"snippets": "1.0.1",
|
||||
"spell-check": "0.63.0",
|
||||
"status-bar": "0.80.0",
|
||||
"styleguide": "0.45.0",
|
||||
"symbols-view": "0.110.0",
|
||||
"tabs": "0.88.0",
|
||||
"timecop": "0.33.0",
|
||||
"tree-view": "0.196.0",
|
||||
"tree-view": "0.198.0",
|
||||
"update-package-dependencies": "0.10.0",
|
||||
"welcome": "0.32.0",
|
||||
"whitespace": "0.32.0",
|
||||
@@ -127,19 +127,19 @@
|
||||
"language-html": "0.42.0",
|
||||
"language-hyperlink": "0.15.0",
|
||||
"language-java": "0.16.1",
|
||||
"language-javascript": "0.98.0",
|
||||
"language-javascript": "0.100.0",
|
||||
"language-json": "0.17.1",
|
||||
"language-less": "0.28.3",
|
||||
"language-make": "0.19.0",
|
||||
"language-make": "0.20.0",
|
||||
"language-mustache": "0.13.0",
|
||||
"language-objective-c": "0.15.0",
|
||||
"language-perl": "0.30.0",
|
||||
"language-php": "0.32.0",
|
||||
"language-perl": "0.31.0",
|
||||
"language-php": "0.34.0",
|
||||
"language-property-list": "0.8.0",
|
||||
"language-python": "0.41.0",
|
||||
"language-ruby": "0.60.0",
|
||||
"language-ruby": "0.61.0",
|
||||
"language-ruby-on-rails": "0.24.0",
|
||||
"language-sass": "0.42.1",
|
||||
"language-sass": "0.43.0",
|
||||
"language-shellscript": "0.20.0",
|
||||
"language-source": "0.9.0",
|
||||
"language-sql": "0.19.0",
|
||||
|
||||
28
spec/async-spec-helpers.coffee
Normal file
28
spec/async-spec-helpers.coffee
Normal file
@@ -0,0 +1,28 @@
|
||||
exports.beforeEach = (fn) ->
|
||||
global.beforeEach ->
|
||||
result = fn()
|
||||
if result instanceof Promise
|
||||
waitsForPromise(-> result)
|
||||
|
||||
exports.afterEach = (fn) ->
|
||||
global.afterEach ->
|
||||
result = fn()
|
||||
if result instanceof Promise
|
||||
waitsForPromise(-> result)
|
||||
|
||||
['it', 'fit', 'ffit', 'fffit'].forEach (name) ->
|
||||
exports[name] = (description, fn) ->
|
||||
global[name] description, ->
|
||||
result = fn()
|
||||
if result instanceof Promise
|
||||
waitsForPromise(-> result)
|
||||
|
||||
waitsForPromise = (fn) ->
|
||||
promise = fn()
|
||||
waitsFor 'spec promise to resolve', 30000, (done) ->
|
||||
promise.then(
|
||||
done,
|
||||
(error) ->
|
||||
jasmine.getEnv().currentSpec.fail(error)
|
||||
done()
|
||||
)
|
||||
@@ -243,23 +243,6 @@ describe "AtomEnvironment", ->
|
||||
|
||||
atomEnvironment.destroy()
|
||||
|
||||
describe "::destroy()", ->
|
||||
it "unsubscribes from all buffers", ->
|
||||
atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate, window, document})
|
||||
|
||||
waitsForPromise ->
|
||||
atomEnvironment.workspace.open("sample.js")
|
||||
|
||||
runs ->
|
||||
buffer = atomEnvironment.workspace.getActivePaneItem().buffer
|
||||
pane = atomEnvironment.workspace.getActivePane()
|
||||
pane.splitRight(copyActiveItem: true)
|
||||
expect(atomEnvironment.workspace.getTextEditors().length).toBe 2
|
||||
|
||||
atomEnvironment.destroy()
|
||||
|
||||
expect(buffer.getSubscriptionCount()).toBe 0
|
||||
|
||||
describe "::openLocations(locations) (called via IPC from browser process)", ->
|
||||
beforeEach ->
|
||||
spyOn(atom.workspace, 'open')
|
||||
|
||||
@@ -418,11 +418,11 @@ describe "DisplayBuffer", ->
|
||||
describe "when creating a fold where one already exists", ->
|
||||
it "returns existing fold and does't create new fold", ->
|
||||
fold = displayBuffer.createFold(0, 10)
|
||||
expect(displayBuffer.findMarkers(class: 'fold').length).toBe 1
|
||||
expect(displayBuffer.foldsMarkerLayer.getMarkers().length).toBe 1
|
||||
|
||||
newFold = displayBuffer.createFold(0, 10)
|
||||
expect(newFold).toBe fold
|
||||
expect(displayBuffer.findMarkers(class: 'fold').length).toBe 1
|
||||
expect(displayBuffer.foldsMarkerLayer.getMarkers().length).toBe 1
|
||||
|
||||
describe "when a fold is created inside an existing folded region", ->
|
||||
it "creates/destroys the fold, but does not trigger change event", ->
|
||||
@@ -829,7 +829,6 @@ describe "DisplayBuffer", ->
|
||||
it "unsubscribes all display buffer markers from their underlying buffer marker (regression)", ->
|
||||
marker = displayBuffer.markBufferPosition([12, 2])
|
||||
displayBuffer.destroy()
|
||||
expect(marker.bufferMarker.getSubscriptionCount()).toBe 0
|
||||
expect( -> buffer.insert([12, 2], '\n')).not.toThrow()
|
||||
|
||||
describe "markers", ->
|
||||
@@ -879,7 +878,7 @@ describe "DisplayBuffer", ->
|
||||
[markerChangedHandler, marker] = []
|
||||
|
||||
beforeEach ->
|
||||
marker = displayBuffer.markScreenRange([[5, 4], [5, 10]], maintainHistory: true)
|
||||
marker = displayBuffer.addMarkerLayer(maintainHistory: true).markScreenRange([[5, 4], [5, 10]])
|
||||
marker.onDidChange markerChangedHandler = jasmine.createSpy("markerChangedHandler")
|
||||
|
||||
it "triggers the 'changed' event whenever the markers head's screen position changes in the buffer or on screen", ->
|
||||
@@ -1016,7 +1015,7 @@ describe "DisplayBuffer", ->
|
||||
expect(markerChangedHandler).not.toHaveBeenCalled()
|
||||
|
||||
it "updates markers before emitting buffer change events, but does not notify their observers until the change event", ->
|
||||
marker2 = displayBuffer.markBufferRange([[8, 1], [8, 1]], maintainHistory: true)
|
||||
marker2 = displayBuffer.addMarkerLayer(maintainHistory: true).markBufferRange([[8, 1], [8, 1]])
|
||||
marker2.onDidChange marker2ChangedHandler = jasmine.createSpy("marker2ChangedHandler")
|
||||
displayBuffer.onDidChange changeHandler = jasmine.createSpy("changeHandler").andCallFake -> onDisplayBufferChange()
|
||||
|
||||
@@ -1237,11 +1236,6 @@ describe "DisplayBuffer", ->
|
||||
decoration.destroy()
|
||||
expect(displayBuffer.decorationForId(decoration.id)).not.toBeDefined()
|
||||
|
||||
it "does not leak disposables", ->
|
||||
disposablesSize = displayBuffer.disposables.disposables.size
|
||||
decoration.destroy()
|
||||
expect(displayBuffer.disposables.disposables.size).toBe(disposablesSize - 1)
|
||||
|
||||
describe "when a decoration is updated via Decoration::update()", ->
|
||||
it "emits an 'updated' event containing the new and old params", ->
|
||||
decoration.onDidChangeProperties updatedSpy = jasmine.createSpy()
|
||||
@@ -1249,7 +1243,7 @@ describe "DisplayBuffer", ->
|
||||
|
||||
{oldProperties, newProperties} = updatedSpy.mostRecentCall.args[0]
|
||||
expect(oldProperties).toEqual decorationProperties
|
||||
expect(newProperties).toEqual type: 'line-number', gutterName: 'line-number', class: 'two', id: decoration.id
|
||||
expect(newProperties).toEqual {type: 'line-number', gutterName: 'line-number', class: 'two'}
|
||||
|
||||
describe "::getDecorations(properties)", ->
|
||||
it "returns decorations matching the given optional properties", ->
|
||||
|
||||
12
spec/fixtures/sample-with-many-folds.js
vendored
Normal file
12
spec/fixtures/sample-with-many-folds.js
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
1;
|
||||
2;
|
||||
function f3() {
|
||||
return 4;
|
||||
};
|
||||
6;
|
||||
7;
|
||||
function f8() {
|
||||
return 9;
|
||||
};
|
||||
11;
|
||||
12;
|
||||
@@ -433,6 +433,13 @@ describe "PackageManager", ->
|
||||
runs ->
|
||||
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element1)).toHaveLength 0
|
||||
|
||||
describe "when setting core.packagesWithKeymapsDisabled", ->
|
||||
it "ignores package names in the array that aren't loaded", ->
|
||||
atom.packages.observePackagesWithKeymapsDisabled()
|
||||
|
||||
expect(-> atom.config.set("core.packagesWithKeymapsDisabled", ["package-does-not-exist"])).not.toThrow()
|
||||
expect(-> atom.config.set("core.packagesWithKeymapsDisabled", [])).not.toThrow()
|
||||
|
||||
describe "when the package's keymaps are disabled and re-enabled after it is activated", ->
|
||||
it "removes and re-adds the keymaps", ->
|
||||
element1 = createTestElement('test-1')
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
undefined
|
||||
File diff suppressed because it is too large
Load Diff
4752
spec/text-editor-component-spec.js
Normal file
4752
spec/text-editor-component-spec.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -69,6 +69,13 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
expectNoStateUpdate = (presenter, fn) -> expectStateUpdatedToBe(false, presenter, fn)
|
||||
|
||||
waitsForStateToUpdate = (presenter, fn) ->
|
||||
waitsFor "presenter state to update", 1000, (done) ->
|
||||
fn?()
|
||||
disposable = presenter.onDidUpdateState ->
|
||||
disposable.dispose()
|
||||
process.nextTick(done)
|
||||
|
||||
tiledContentContract = (stateFn) ->
|
||||
it "contains states for tiles that are visible on screen", ->
|
||||
presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2)
|
||||
@@ -1162,55 +1169,62 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
describe ".decorationClasses", ->
|
||||
it "adds decoration classes to the relevant line state objects, both initially and when decorations change", ->
|
||||
marker1 = editor.markBufferRange([[4, 0], [6, 2]], invalidate: 'touch', maintainHistory: true)
|
||||
marker1 = editor.addMarkerLayer(maintainHistory: true).markBufferRange([[4, 0], [6, 2]], invalidate: 'touch')
|
||||
decoration1 = editor.decorateMarker(marker1, type: 'line', class: 'a')
|
||||
presenter = buildPresenter()
|
||||
marker2 = editor.markBufferRange([[4, 0], [6, 2]], invalidate: 'touch', maintainHistory: true)
|
||||
marker2 = editor.addMarkerLayer(maintainHistory: true).markBufferRange([[4, 0], [6, 2]], invalidate: 'touch')
|
||||
decoration2 = editor.decorateMarker(marker2, type: 'line', class: 'b')
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> editor.getBuffer().insert([5, 0], 'x')
|
||||
expect(marker1.isValid()).toBe false
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter, -> editor.getBuffer().insert([5, 0], 'x')
|
||||
runs ->
|
||||
expect(marker1.isValid()).toBe false
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> editor.undo()
|
||||
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter, -> editor.undo()
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> marker1.setBufferRange([[2, 0], [4, 2]])
|
||||
expect(lineStateForScreenRow(presenter, 1).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 2).decorationClasses).toEqual ['a']
|
||||
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toEqual ['a']
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
|
||||
expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter, -> marker1.setBufferRange([[2, 0], [4, 2]])
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 1).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 2).decorationClasses).toEqual ['a']
|
||||
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toEqual ['a']
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
|
||||
expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> decoration1.destroy()
|
||||
expect(lineStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['b']
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
|
||||
expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter, -> decoration1.destroy()
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['b']
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
|
||||
expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> marker2.destroy()
|
||||
expect(lineStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter, -> marker2.destroy()
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
|
||||
it "honors the 'onlyEmpty' option on line decorations", ->
|
||||
presenter = buildPresenter()
|
||||
@@ -1221,11 +1235,12 @@ describe "TextEditorPresenter", ->
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> marker.clearTail()
|
||||
waitsForStateToUpdate presenter, -> marker.clearTail()
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a']
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a']
|
||||
|
||||
it "honors the 'onlyNonEmpty' option on line decorations", ->
|
||||
presenter = buildPresenter()
|
||||
@@ -1236,40 +1251,49 @@ describe "TextEditorPresenter", ->
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a']
|
||||
|
||||
expectStateUpdate presenter, -> marker.clearTail()
|
||||
waitsForStateToUpdate presenter, -> marker.clearTail()
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
|
||||
it "honors the 'onlyHead' option on line decorations", ->
|
||||
presenter = buildPresenter()
|
||||
marker = editor.markBufferRange([[4, 0], [6, 2]])
|
||||
decoration = editor.decorateMarker(marker, type: 'line', class: 'a', onlyHead: true)
|
||||
waitsForStateToUpdate presenter, ->
|
||||
marker = editor.markBufferRange([[4, 0], [6, 2]])
|
||||
editor.decorateMarker(marker, type: 'line', class: 'a', onlyHead: true)
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a']
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a']
|
||||
|
||||
it "does not decorate the last line of a non-empty line decoration range if it ends at column 0", ->
|
||||
presenter = buildPresenter()
|
||||
marker = editor.markBufferRange([[4, 0], [6, 0]])
|
||||
decoration = editor.decorateMarker(marker, type: 'line', class: 'a')
|
||||
waitsForStateToUpdate presenter, ->
|
||||
marker = editor.markBufferRange([[4, 0], [6, 0]])
|
||||
editor.decorateMarker(marker, type: 'line', class: 'a')
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a']
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a']
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
|
||||
it "does not apply line decorations to mini editors", ->
|
||||
editor.setMini(true)
|
||||
presenter = buildPresenter(explicitHeight: 10)
|
||||
marker = editor.markBufferRange([[0, 0], [0, 0]])
|
||||
decoration = editor.decorateMarker(marker, type: 'line', class: 'a')
|
||||
expect(lineStateForScreenRow(presenter, 0).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> editor.setMini(false)
|
||||
expect(lineStateForScreenRow(presenter, 0).decorationClasses).toEqual ['cursor-line', 'a']
|
||||
waitsForStateToUpdate presenter, ->
|
||||
marker = editor.markBufferRange([[0, 0], [0, 0]])
|
||||
decoration = editor.decorateMarker(marker, type: 'line', class: 'a')
|
||||
|
||||
expectStateUpdate presenter, -> editor.setMini(true)
|
||||
expect(lineStateForScreenRow(presenter, 0).decorationClasses).toBeNull()
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 0).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> editor.setMini(false)
|
||||
expect(lineStateForScreenRow(presenter, 0).decorationClasses).toEqual ['cursor-line', 'a']
|
||||
|
||||
expectStateUpdate presenter, -> editor.setMini(true)
|
||||
expect(lineStateForScreenRow(presenter, 0).decorationClasses).toBeNull()
|
||||
|
||||
it "only applies decorations to screen rows that are spanned by their marker when lines are soft-wrapped", ->
|
||||
editor.setText("a line that wraps, ok")
|
||||
@@ -1283,9 +1307,12 @@ describe "TextEditorPresenter", ->
|
||||
expect(lineStateForScreenRow(presenter, 0).decorationClasses).toContain 'a'
|
||||
expect(lineStateForScreenRow(presenter, 1).decorationClasses).toBeNull()
|
||||
|
||||
marker.setBufferRange([[0, 0], [0, Infinity]])
|
||||
expect(lineStateForScreenRow(presenter, 0).decorationClasses).toContain 'a'
|
||||
expect(lineStateForScreenRow(presenter, 1).decorationClasses).toContain 'a'
|
||||
waitsForStateToUpdate presenter, ->
|
||||
marker.setBufferRange([[0, 0], [0, Infinity]])
|
||||
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 0).decorationClasses).toContain 'a'
|
||||
expect(lineStateForScreenRow(presenter, 1).decorationClasses).toContain 'a'
|
||||
|
||||
describe ".cursors", ->
|
||||
stateForCursor = (presenter, cursorIndex) ->
|
||||
@@ -1766,41 +1793,51 @@ describe "TextEditorPresenter", ->
|
||||
expectUndefinedStateForSelection(presenter, 1)
|
||||
|
||||
# moving into view
|
||||
expectStateUpdate presenter, -> editor.getSelections()[1].setBufferRange([[2, 4], [2, 6]], autoscroll: false)
|
||||
expectValues stateForSelectionInTile(presenter, 1, 2), {
|
||||
regions: [{top: 0, left: 4 * 10, width: 2 * 10, height: 10}]
|
||||
}
|
||||
waitsForStateToUpdate presenter, -> editor.getSelections()[1].setBufferRange([[2, 4], [2, 6]], autoscroll: false)
|
||||
runs ->
|
||||
expectValues stateForSelectionInTile(presenter, 1, 2), {
|
||||
regions: [{top: 0, left: 4 * 10, width: 2 * 10, height: 10}]
|
||||
}
|
||||
|
||||
# becoming empty
|
||||
expectStateUpdate presenter, -> editor.getSelections()[1].clear(autoscroll: false)
|
||||
expectUndefinedStateForSelection(presenter, 1)
|
||||
waitsForStateToUpdate presenter, -> editor.getSelections()[1].clear(autoscroll: false)
|
||||
runs ->
|
||||
expectUndefinedStateForSelection(presenter, 1)
|
||||
|
||||
# becoming non-empty
|
||||
expectStateUpdate presenter, -> editor.getSelections()[1].setBufferRange([[2, 4], [2, 6]], autoscroll: false)
|
||||
expectValues stateForSelectionInTile(presenter, 1, 2), {
|
||||
regions: [{top: 0, left: 4 * 10, width: 2 * 10, height: 10}]
|
||||
}
|
||||
waitsForStateToUpdate presenter, -> editor.getSelections()[1].setBufferRange([[2, 4], [2, 6]], autoscroll: false)
|
||||
runs ->
|
||||
expectValues stateForSelectionInTile(presenter, 1, 2), {
|
||||
regions: [{top: 0, left: 4 * 10, width: 2 * 10, height: 10}]
|
||||
}
|
||||
|
||||
# moving out of view
|
||||
expectStateUpdate presenter, -> editor.getSelections()[1].setBufferRange([[3, 4], [3, 6]], autoscroll: false)
|
||||
expectUndefinedStateForSelection(presenter, 1)
|
||||
waitsForStateToUpdate presenter, -> editor.getSelections()[1].setBufferRange([[3, 4], [3, 6]], autoscroll: false)
|
||||
runs ->
|
||||
expectUndefinedStateForSelection(presenter, 1)
|
||||
|
||||
# adding
|
||||
expectStateUpdate presenter, -> editor.addSelectionForBufferRange([[1, 4], [1, 6]], autoscroll: false)
|
||||
expectValues stateForSelectionInTile(presenter, 2, 0), {
|
||||
regions: [{top: 10, left: 4 * 10, width: 2 * 10, height: 10}]
|
||||
}
|
||||
waitsForStateToUpdate presenter, -> editor.addSelectionForBufferRange([[1, 4], [1, 6]], autoscroll: false)
|
||||
runs ->
|
||||
expectValues stateForSelectionInTile(presenter, 2, 0), {
|
||||
regions: [{top: 10, left: 4 * 10, width: 2 * 10, height: 10}]
|
||||
}
|
||||
|
||||
# moving added selection
|
||||
expectStateUpdate presenter, -> editor.getSelections()[2].setBufferRange([[1, 4], [1, 8]], autoscroll: false)
|
||||
expectValues stateForSelectionInTile(presenter, 2, 0), {
|
||||
regions: [{top: 10, left: 4 * 10, width: 4 * 10, height: 10}]
|
||||
}
|
||||
waitsForStateToUpdate presenter, -> editor.getSelections()[2].setBufferRange([[1, 4], [1, 8]], autoscroll: false)
|
||||
|
||||
# destroying
|
||||
destroyedSelection = editor.getSelections()[2]
|
||||
expectStateUpdate presenter, -> destroyedSelection.destroy()
|
||||
expectUndefinedStateForHighlight(presenter, destroyedSelection.decoration)
|
||||
destroyedSelection = null
|
||||
runs ->
|
||||
expectValues stateForSelectionInTile(presenter, 2, 0), {
|
||||
regions: [{top: 10, left: 4 * 10, width: 4 * 10, height: 10}]
|
||||
}
|
||||
|
||||
# destroying
|
||||
destroyedSelection = editor.getSelections()[2]
|
||||
|
||||
waitsForStateToUpdate presenter, -> destroyedSelection.destroy()
|
||||
runs ->
|
||||
expectUndefinedStateForHighlight(presenter, destroyedSelection.decoration)
|
||||
|
||||
it "updates when highlight decorations' properties are updated", ->
|
||||
marker = editor.markBufferPosition([2, 2])
|
||||
@@ -1810,44 +1847,45 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
expectUndefinedStateForHighlight(presenter, highlight)
|
||||
|
||||
expectStateUpdate presenter, ->
|
||||
waitsForStateToUpdate presenter, ->
|
||||
marker.setBufferRange([[2, 2], [2, 4]])
|
||||
highlight.setProperties(class: 'b', type: 'highlight')
|
||||
|
||||
expectValues stateForHighlightInTile(presenter, highlight, 2), {class: 'b'}
|
||||
runs ->
|
||||
expectValues stateForHighlightInTile(presenter, highlight, 2), {class: 'b'}
|
||||
|
||||
it "increments the .flashCount and sets the .flashClass and .flashDuration when the highlight model flashes", ->
|
||||
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20, tileSize: 2)
|
||||
|
||||
marker = editor.markBufferPosition([2, 2])
|
||||
highlight = editor.decorateMarker(marker, type: 'highlight', class: 'a')
|
||||
expectStateUpdate presenter, ->
|
||||
waitsForStateToUpdate presenter, ->
|
||||
marker.setBufferRange([[2, 2], [5, 2]])
|
||||
highlight.flash('b', 500)
|
||||
runs ->
|
||||
expectValues stateForHighlightInTile(presenter, highlight, 2), {
|
||||
flashClass: 'b'
|
||||
flashDuration: 500
|
||||
flashCount: 1
|
||||
}
|
||||
expectValues stateForHighlightInTile(presenter, highlight, 4), {
|
||||
flashClass: 'b'
|
||||
flashDuration: 500
|
||||
flashCount: 1
|
||||
}
|
||||
|
||||
expectValues stateForHighlightInTile(presenter, highlight, 2), {
|
||||
flashClass: 'b'
|
||||
flashDuration: 500
|
||||
flashCount: 1
|
||||
}
|
||||
expectValues stateForHighlightInTile(presenter, highlight, 4), {
|
||||
flashClass: 'b'
|
||||
flashDuration: 500
|
||||
flashCount: 1
|
||||
}
|
||||
|
||||
expectStateUpdate presenter, -> highlight.flash('c', 600)
|
||||
|
||||
expectValues stateForHighlightInTile(presenter, highlight, 2), {
|
||||
flashClass: 'c'
|
||||
flashDuration: 600
|
||||
flashCount: 2
|
||||
}
|
||||
expectValues stateForHighlightInTile(presenter, highlight, 4), {
|
||||
flashClass: 'c'
|
||||
flashDuration: 600
|
||||
flashCount: 2
|
||||
}
|
||||
waitsForStateToUpdate presenter, -> highlight.flash('c', 600)
|
||||
runs ->
|
||||
expectValues stateForHighlightInTile(presenter, highlight, 2), {
|
||||
flashClass: 'c'
|
||||
flashDuration: 600
|
||||
flashCount: 2
|
||||
}
|
||||
expectValues stateForHighlightInTile(presenter, highlight, 4), {
|
||||
flashClass: 'c'
|
||||
flashDuration: 600
|
||||
flashCount: 2
|
||||
}
|
||||
|
||||
describe ".overlays", ->
|
||||
[item] = []
|
||||
@@ -1855,7 +1893,7 @@ describe "TextEditorPresenter", ->
|
||||
presenter.getState().content.overlays[decoration.id]
|
||||
|
||||
it "contains state for overlay decorations both initially and when their markers move", ->
|
||||
marker = editor.markBufferPosition([2, 13], invalidate: 'touch', maintainHistory: true)
|
||||
marker = editor.addMarkerLayer(maintainHistory: true).markBufferPosition([2, 13], invalidate: 'touch')
|
||||
decoration = editor.decorateMarker(marker, {type: 'overlay', item})
|
||||
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20)
|
||||
|
||||
@@ -1866,40 +1904,47 @@ describe "TextEditorPresenter", ->
|
||||
}
|
||||
|
||||
# Change range
|
||||
expectStateUpdate presenter, -> marker.setBufferRange([[2, 13], [4, 6]])
|
||||
expectValues stateForOverlay(presenter, decoration), {
|
||||
item: item
|
||||
pixelPosition: {top: 5 * 10 - presenter.state.content.scrollTop, left: 6 * 10}
|
||||
}
|
||||
waitsForStateToUpdate presenter, -> marker.setBufferRange([[2, 13], [4, 6]])
|
||||
runs ->
|
||||
expectValues stateForOverlay(presenter, decoration), {
|
||||
item: item
|
||||
pixelPosition: {top: 5 * 10 - presenter.state.content.scrollTop, left: 6 * 10}
|
||||
}
|
||||
|
||||
# Valid -> invalid
|
||||
expectStateUpdate presenter, -> editor.getBuffer().insert([2, 14], 'x')
|
||||
expect(stateForOverlay(presenter, decoration)).toBeUndefined()
|
||||
# Valid -> invalid
|
||||
waitsForStateToUpdate presenter, -> editor.getBuffer().insert([2, 14], 'x')
|
||||
runs ->
|
||||
expect(stateForOverlay(presenter, decoration)).toBeUndefined()
|
||||
|
||||
# Invalid -> valid
|
||||
expectStateUpdate presenter, -> editor.undo()
|
||||
expectValues stateForOverlay(presenter, decoration), {
|
||||
item: item
|
||||
pixelPosition: {top: 5 * 10 - presenter.state.content.scrollTop, left: 6 * 10}
|
||||
}
|
||||
# Invalid -> valid
|
||||
waitsForStateToUpdate presenter, -> editor.undo()
|
||||
runs ->
|
||||
expectValues stateForOverlay(presenter, decoration), {
|
||||
item: item
|
||||
pixelPosition: {top: 5 * 10 - presenter.state.content.scrollTop, left: 6 * 10}
|
||||
}
|
||||
|
||||
# Reverse direction
|
||||
expectStateUpdate presenter, -> marker.setBufferRange([[2, 13], [4, 6]], reversed: true)
|
||||
expectValues stateForOverlay(presenter, decoration), {
|
||||
item: item
|
||||
pixelPosition: {top: 3 * 10 - presenter.state.content.scrollTop, left: 13 * 10}
|
||||
}
|
||||
waitsForStateToUpdate presenter, -> marker.setBufferRange([[2, 13], [4, 6]], reversed: true)
|
||||
runs ->
|
||||
expectValues stateForOverlay(presenter, decoration), {
|
||||
item: item
|
||||
pixelPosition: {top: 3 * 10 - presenter.state.content.scrollTop, left: 13 * 10}
|
||||
}
|
||||
|
||||
# Destroy
|
||||
decoration.destroy()
|
||||
expect(stateForOverlay(presenter, decoration)).toBeUndefined()
|
||||
waitsForStateToUpdate presenter, -> decoration.destroy()
|
||||
runs ->
|
||||
expect(stateForOverlay(presenter, decoration)).toBeUndefined()
|
||||
|
||||
# Add
|
||||
decoration2 = editor.decorateMarker(marker, {type: 'overlay', item})
|
||||
expectValues stateForOverlay(presenter, decoration2), {
|
||||
item: item
|
||||
pixelPosition: {top: 3 * 10 - presenter.state.content.scrollTop, left: 13 * 10}
|
||||
}
|
||||
decoration2 = null
|
||||
waitsForStateToUpdate presenter, -> decoration2 = editor.decorateMarker(marker, {type: 'overlay', item})
|
||||
runs ->
|
||||
expectValues stateForOverlay(presenter, decoration2), {
|
||||
item: item
|
||||
pixelPosition: {top: 3 * 10 - presenter.state.content.scrollTop, left: 13 * 10}
|
||||
}
|
||||
|
||||
it "updates when character widths changes", ->
|
||||
scrollTop = 20
|
||||
@@ -2343,11 +2388,11 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
describe ".decorationClasses", ->
|
||||
it "adds decoration classes to the relevant line number state objects, both initially and when decorations change", ->
|
||||
marker1 = editor.markBufferRange([[4, 0], [6, 2]], invalidate: 'touch', maintainHistory: true)
|
||||
marker1 = editor.addMarkerLayer(maintainHistory: true).markBufferRange([[4, 0], [6, 2]], invalidate: 'touch')
|
||||
decoration1 = editor.decorateMarker(marker1, type: 'line-number', class: 'a')
|
||||
presenter = buildPresenter()
|
||||
marker2 = editor.markBufferRange([[4, 0], [6, 2]], invalidate: 'touch', maintainHistory: true)
|
||||
marker2 = editor.addMarkerLayer(maintainHistory: true).markBufferRange([[4, 0], [6, 2]], invalidate: 'touch')
|
||||
decoration2 = editor.decorateMarker(marker2, type: 'line-number', class: 'b')
|
||||
presenter = buildPresenter()
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
@@ -2355,85 +2400,92 @@ describe "TextEditorPresenter", ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> editor.getBuffer().insert([5, 0], 'x')
|
||||
expect(marker1.isValid()).toBe false
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter, -> editor.getBuffer().insert([5, 0], 'x')
|
||||
runs ->
|
||||
expect(marker1.isValid()).toBe false
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> editor.undo()
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter, -> editor.undo()
|
||||
runs ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> marker1.setBufferRange([[2, 0], [4, 2]])
|
||||
expect(lineNumberStateForScreenRow(presenter, 1).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toEqual ['a']
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toEqual ['a']
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter, -> marker1.setBufferRange([[2, 0], [4, 2]])
|
||||
runs ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 1).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toEqual ['a']
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toEqual ['a']
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> decoration1.destroy()
|
||||
expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter, -> decoration1.destroy()
|
||||
runs ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> marker2.destroy()
|
||||
expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter, -> marker2.destroy()
|
||||
runs ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
|
||||
it "honors the 'onlyEmpty' option on line-number decorations", ->
|
||||
presenter = buildPresenter()
|
||||
marker = editor.markBufferRange([[4, 0], [6, 1]])
|
||||
decoration = editor.decorateMarker(marker, type: 'line-number', class: 'a', onlyEmpty: true)
|
||||
presenter = buildPresenter()
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> marker.clearTail()
|
||||
waitsForStateToUpdate presenter, -> marker.clearTail()
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a']
|
||||
runs ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a']
|
||||
|
||||
it "honors the 'onlyNonEmpty' option on line-number decorations", ->
|
||||
presenter = buildPresenter()
|
||||
marker = editor.markBufferRange([[4, 0], [6, 2]])
|
||||
decoration = editor.decorateMarker(marker, type: 'line-number', class: 'a', onlyNonEmpty: true)
|
||||
presenter = buildPresenter()
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a']
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a']
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a']
|
||||
|
||||
expectStateUpdate presenter, -> marker.clearTail()
|
||||
waitsForStateToUpdate presenter, -> marker.clearTail()
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
runs ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
|
||||
it "honors the 'onlyHead' option on line-number decorations", ->
|
||||
presenter = buildPresenter()
|
||||
marker = editor.markBufferRange([[4, 0], [6, 2]])
|
||||
decoration = editor.decorateMarker(marker, type: 'line-number', class: 'a', onlyHead: true)
|
||||
presenter = buildPresenter()
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a']
|
||||
|
||||
it "does not decorate the last line of a non-empty line-number decoration range if it ends at column 0", ->
|
||||
presenter = buildPresenter()
|
||||
marker = editor.markBufferRange([[4, 0], [6, 0]])
|
||||
decoration = editor.decorateMarker(marker, type: 'line-number', class: 'a')
|
||||
presenter = buildPresenter()
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a']
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a']
|
||||
@@ -2465,9 +2517,10 @@ describe "TextEditorPresenter", ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 0).decorationClasses).toContain 'a'
|
||||
expect(lineNumberStateForScreenRow(presenter, 1).decorationClasses).toBeNull()
|
||||
|
||||
marker.setBufferRange([[0, 0], [0, Infinity]])
|
||||
expect(lineNumberStateForScreenRow(presenter, 0).decorationClasses).toContain 'a'
|
||||
expect(lineNumberStateForScreenRow(presenter, 1).decorationClasses).toContain 'a'
|
||||
waitsForStateToUpdate presenter, -> marker.setBufferRange([[0, 0], [0, Infinity]])
|
||||
runs ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 0).decorationClasses).toContain 'a'
|
||||
expect(lineNumberStateForScreenRow(presenter, 1).decorationClasses).toContain 'a'
|
||||
|
||||
describe ".foldable", ->
|
||||
it "marks line numbers at the start of a foldable region as foldable", ->
|
||||
@@ -2600,14 +2653,15 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
it "updates when a decoration's marker is modified", ->
|
||||
# This update will move decoration1 out of view.
|
||||
expectStateUpdate presenter, ->
|
||||
waitsForStateToUpdate presenter, ->
|
||||
newRange = new Range([13, 0], [14, 0])
|
||||
marker1.setBufferRange(newRange)
|
||||
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration2.id].top).toBeDefined()
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
runs ->
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration2.id].top).toBeDefined()
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
|
||||
describe "when a decoration's properties are modified", ->
|
||||
it "updates the item applied to the decoration, if the decoration item is changed", ->
|
||||
@@ -2619,12 +2673,14 @@ describe "TextEditorPresenter", ->
|
||||
gutterName: 'test-gutter'
|
||||
class: 'test-class'
|
||||
item: newItem
|
||||
expectStateUpdate presenter, -> decoration1.setProperties(newDecorationParams)
|
||||
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id].item).toBe newItem
|
||||
expect(decorationState[decoration2.id].item).toBe decorationItem
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
waitsForStateToUpdate presenter, -> decoration1.setProperties(newDecorationParams)
|
||||
|
||||
runs ->
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id].item).toBe newItem
|
||||
expect(decorationState[decoration2.id].item).toBe decorationItem
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
|
||||
it "updates the class applied to the decoration, if the decoration class is changed", ->
|
||||
# This changes the decoration item. The visibility of the decoration should not be affected.
|
||||
@@ -2633,12 +2689,13 @@ describe "TextEditorPresenter", ->
|
||||
gutterName: 'test-gutter'
|
||||
class: 'new-test-class'
|
||||
item: decorationItem
|
||||
expectStateUpdate presenter, -> decoration1.setProperties(newDecorationParams)
|
||||
waitsForStateToUpdate presenter, -> decoration1.setProperties(newDecorationParams)
|
||||
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id].class).toBe 'new-test-class'
|
||||
expect(decorationState[decoration2.id].class).toBe 'test-class'
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
runs ->
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id].class).toBe 'new-test-class'
|
||||
expect(decorationState[decoration2.id].class).toBe 'test-class'
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
|
||||
it "updates the type of the decoration, if the decoration type is changed", ->
|
||||
# This changes the type of the decoration. This should remove the decoration from the gutter.
|
||||
@@ -2647,12 +2704,13 @@ describe "TextEditorPresenter", ->
|
||||
gutterName: 'test-gutter' # This is an invalid/meaningless option here, but it shouldn't matter.
|
||||
class: 'test-class'
|
||||
item: decorationItem
|
||||
expectStateUpdate presenter, -> decoration1.setProperties(newDecorationParams)
|
||||
waitsForStateToUpdate presenter, -> decoration1.setProperties(newDecorationParams)
|
||||
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration2.id].top).toBeDefined()
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
runs ->
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration2.id].top).toBeDefined()
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
|
||||
it "updates the gutter the decoration targets, if the decoration gutterName is changed", ->
|
||||
# This changes which gutter this decoration applies to. Since this gutter does not exist,
|
||||
@@ -2662,24 +2720,25 @@ describe "TextEditorPresenter", ->
|
||||
gutterName: 'test-gutter-2'
|
||||
class: 'new-test-class'
|
||||
item: decorationItem
|
||||
expectStateUpdate presenter, -> decoration1.setProperties(newDecorationParams)
|
||||
waitsForStateToUpdate presenter, -> decoration1.setProperties(newDecorationParams)
|
||||
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration2.id].top).toBeDefined()
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
runs ->
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration2.id].top).toBeDefined()
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
|
||||
# After adding the targeted gutter, the decoration will appear in the state for that gutter,
|
||||
# since it should be visible.
|
||||
expectStateUpdate presenter, -> editor.addGutter({name: 'test-gutter-2'})
|
||||
newGutterDecorationState = getContentForGutterWithName(presenter, 'test-gutter-2')
|
||||
expect(newGutterDecorationState[decoration1.id].top).toBeDefined()
|
||||
expect(newGutterDecorationState[decoration2.id]).toBeUndefined()
|
||||
expect(newGutterDecorationState[decoration3.id]).toBeUndefined()
|
||||
oldGutterDecorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(oldGutterDecorationState[decoration1.id]).toBeUndefined()
|
||||
expect(oldGutterDecorationState[decoration2.id].top).toBeDefined()
|
||||
expect(oldGutterDecorationState[decoration3.id]).toBeUndefined()
|
||||
# After adding the targeted gutter, the decoration will appear in the state for that gutter,
|
||||
# since it should be visible.
|
||||
expectStateUpdate presenter, -> editor.addGutter({name: 'test-gutter-2'})
|
||||
newGutterDecorationState = getContentForGutterWithName(presenter, 'test-gutter-2')
|
||||
expect(newGutterDecorationState[decoration1.id].top).toBeDefined()
|
||||
expect(newGutterDecorationState[decoration2.id]).toBeUndefined()
|
||||
expect(newGutterDecorationState[decoration3.id]).toBeUndefined()
|
||||
oldGutterDecorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(oldGutterDecorationState[decoration1.id]).toBeUndefined()
|
||||
expect(oldGutterDecorationState[decoration2.id].top).toBeDefined()
|
||||
expect(oldGutterDecorationState[decoration3.id]).toBeUndefined()
|
||||
|
||||
it "updates when the editor's mini state changes, and is cleared when the editor is mini", ->
|
||||
expectStateUpdate presenter, -> editor.setMini(true)
|
||||
@@ -2714,13 +2773,17 @@ describe "TextEditorPresenter", ->
|
||||
class: 'test-class'
|
||||
marker4 = editor.markBufferRange([[0, 0], [1, 0]])
|
||||
decoration4 = editor.decorateMarker(marker4, decorationParams)
|
||||
expectStateUpdate presenter, -> editor.addGutter({name: 'test-gutter-2'})
|
||||
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter-2')
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration2.id]).toBeUndefined()
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
expect(decorationState[decoration4.id].top).toBeDefined()
|
||||
waitsForStateToUpdate presenter
|
||||
|
||||
runs ->
|
||||
expectStateUpdate presenter, -> editor.addGutter({name: 'test-gutter-2'})
|
||||
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter-2')
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration2.id]).toBeUndefined()
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
expect(decorationState[decoration4.id].top).toBeDefined()
|
||||
|
||||
it "updates when editor lines are folded", ->
|
||||
oldDimensionsForDecoration1 =
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -202,12 +202,12 @@ describe "TokenizedBuffer", ->
|
||||
|
||||
# previous line 3 should be combined with input to form line 1
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: 'foo', scopes: ['source.js'])
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[6]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js'])
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[6]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js'])
|
||||
|
||||
# lines below deleted regions should be shifted upward
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[2]).toEqual(value: 'while', scopes: ['source.js', 'keyword.control.js'])
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[4]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js'])
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[4]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.js'])
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[4]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js'])
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[4]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.comparison.js'])
|
||||
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
[event] = changeHandler.argsForCall[0]
|
||||
@@ -254,7 +254,7 @@ describe "TokenizedBuffer", ->
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[4]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
|
||||
|
||||
# previous line 3 is pushed down to become line 5
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[4]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.js'])
|
||||
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[4]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js'])
|
||||
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
[event] = changeHandler.argsForCall[0]
|
||||
|
||||
@@ -209,3 +209,21 @@ describe "ViewRegistry", ->
|
||||
window.dispatchEvent(new UIEvent('resize'))
|
||||
|
||||
expect(events).toEqual ['poll 1', 'poll 2']
|
||||
|
||||
describe "::getNextUpdatePromise()", ->
|
||||
it "returns a promise that resolves at the end of the next update cycle", ->
|
||||
updateCalled = false
|
||||
readCalled = false
|
||||
pollCalled = false
|
||||
|
||||
waitsFor 'getNextUpdatePromise to resolve', (done) ->
|
||||
registry.getNextUpdatePromise().then ->
|
||||
expect(updateCalled).toBe true
|
||||
expect(readCalled).toBe true
|
||||
expect(pollCalled).toBe true
|
||||
done()
|
||||
|
||||
registry.updateDocument -> updateCalled = true
|
||||
registry.readDocument -> readCalled = true
|
||||
registry.pollDocument -> pollCalled = true
|
||||
registry.pollAfterNextUpdate()
|
||||
|
||||
@@ -496,7 +496,7 @@ class AtomApplication
|
||||
# :specPath - The directory to load specs from.
|
||||
# :safeMode - A Boolean that, if true, won't run specs from ~/.atom/packages
|
||||
# and ~/.atom/dev/packages, defaults to false.
|
||||
runTests: ({headless, devMode, resourcePath, executedFrom, pathsToOpen, logFile, safeMode, timeout}) ->
|
||||
runTests: ({headless, resourcePath, executedFrom, pathsToOpen, logFile, safeMode, timeout}) ->
|
||||
if resourcePath isnt @resourcePath and not fs.existsSync(resourcePath)
|
||||
resourcePath = @resourcePath
|
||||
|
||||
@@ -523,6 +523,7 @@ class AtomApplication
|
||||
|
||||
legacyTestRunnerPath = @resolveLegacyTestRunnerPath()
|
||||
testRunnerPath = @resolveTestRunnerPath(testPaths[0])
|
||||
devMode = true
|
||||
isSpec = true
|
||||
safeMode ?= false
|
||||
new AtomWindow({windowInitializationScript, resourcePath, headless, isSpec, devMode, testRunnerPath, legacyTestRunnerPath, testPaths, logFile, safeMode})
|
||||
|
||||
@@ -28,7 +28,6 @@ class AtomWindow
|
||||
title: 'Atom'
|
||||
'web-preferences':
|
||||
'direct-write': true
|
||||
'subpixel-font-scaling': true
|
||||
|
||||
if @isSpec
|
||||
options['web-preferences']['page-visibility'] = true
|
||||
|
||||
@@ -7,7 +7,7 @@ Model = require './model'
|
||||
# where text can be inserted.
|
||||
#
|
||||
# Cursors belong to {TextEditor}s and have some metadata attached in the form
|
||||
# of a {Marker}.
|
||||
# of a {TextEditorMarker}.
|
||||
module.exports =
|
||||
class Cursor extends Model
|
||||
screenPosition: null
|
||||
@@ -127,7 +127,7 @@ class Cursor extends Model
|
||||
Section: Cursor Position Details
|
||||
###
|
||||
|
||||
# Public: Returns the underlying {Marker} for the cursor.
|
||||
# Public: Returns the underlying {TextEditorMarker} for the cursor.
|
||||
# Useful with overlay {Decoration}s.
|
||||
getMarker: -> @marker
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ translateDecorationParamsOldToNew = (decorationParams) ->
|
||||
decorationParams.gutterName = 'line-number'
|
||||
decorationParams
|
||||
|
||||
# Essential: Represents a decoration that follows a {Marker}. A decoration is
|
||||
# Essential: Represents a decoration that follows a {TextEditorMarker}. A decoration is
|
||||
# basically a visual representation of a marker. It allows you to add CSS
|
||||
# classes to line numbers in the gutter, lines, and add selection-line regions
|
||||
# around marked ranges of text.
|
||||
@@ -25,7 +25,7 @@ translateDecorationParamsOldToNew = (decorationParams) ->
|
||||
# decoration = editor.decorateMarker(marker, {type: 'line', class: 'my-line-class'})
|
||||
# ```
|
||||
#
|
||||
# Best practice for destroying the decoration is by destroying the {Marker}.
|
||||
# Best practice for destroying the decoration is by destroying the {TextEditorMarker}.
|
||||
#
|
||||
# ```coffee
|
||||
# marker.destroy()
|
||||
@@ -67,20 +67,19 @@ class Decoration
|
||||
@emitter = new Emitter
|
||||
@id = nextId()
|
||||
@setProperties properties
|
||||
@properties.id = @id
|
||||
@flashQueue = null
|
||||
@destroyed = false
|
||||
@markerDestroyDisposable = @marker.onDidDestroy => @destroy()
|
||||
|
||||
# Essential: Destroy this marker.
|
||||
#
|
||||
# If you own the marker, you should use {Marker::destroy} which will destroy
|
||||
# If you own the marker, you should use {TextEditorMarker::destroy} which will destroy
|
||||
# this decoration.
|
||||
destroy: ->
|
||||
return if @destroyed
|
||||
@markerDestroyDisposable.dispose()
|
||||
@markerDestroyDisposable = null
|
||||
@destroyed = true
|
||||
@displayBuffer.didDestroyDecoration(this)
|
||||
@emitter.emit 'did-destroy'
|
||||
@emitter.dispose()
|
||||
|
||||
@@ -150,9 +149,9 @@ class Decoration
|
||||
return if @destroyed
|
||||
oldProperties = @properties
|
||||
@properties = translateDecorationParamsOldToNew(newProperties)
|
||||
@properties.id = @id
|
||||
if newProperties.type?
|
||||
@displayBuffer.decorationDidChangeType(this)
|
||||
@displayBuffer.scheduleUpdateDecorationsEvent()
|
||||
@emitter.emit 'did-change-properties', {oldProperties, newProperties}
|
||||
|
||||
###
|
||||
@@ -165,15 +164,10 @@ class Decoration
|
||||
return false if @properties[key] isnt value
|
||||
true
|
||||
|
||||
onDidFlash: (callback) ->
|
||||
@emitter.on 'did-flash', callback
|
||||
|
||||
flash: (klass, duration=500) ->
|
||||
flashObject = {class: klass, duration}
|
||||
@flashQueue ?= []
|
||||
@flashQueue.push(flashObject)
|
||||
@properties.flashCount ?= 0
|
||||
@properties.flashCount++
|
||||
@properties.flashClass = klass
|
||||
@properties.flashDuration = duration
|
||||
@displayBuffer.scheduleUpdateDecorationsEvent()
|
||||
@emitter.emit 'did-flash'
|
||||
|
||||
consumeNextFlash: ->
|
||||
return @flashQueue.shift() if @flashQueue?.length > 0
|
||||
null
|
||||
|
||||
@@ -7,7 +7,8 @@ Fold = require './fold'
|
||||
Model = require './model'
|
||||
Token = require './token'
|
||||
Decoration = require './decoration'
|
||||
Marker = require './marker'
|
||||
LayerDecoration = require './layer-decoration'
|
||||
TextEditorMarkerLayer = require './text-editor-marker-layer'
|
||||
|
||||
class BufferToScreenConversionError extends Error
|
||||
constructor: (@message, @metadata) ->
|
||||
@@ -25,9 +26,12 @@ class DisplayBuffer extends Model
|
||||
defaultCharWidth: null
|
||||
height: null
|
||||
width: null
|
||||
didUpdateDecorationsEventScheduled: false
|
||||
updatedSynchronously: false
|
||||
|
||||
@deserialize: (state, atomEnvironment) ->
|
||||
state.tokenizedBuffer = TokenizedBuffer.deserialize(state.tokenizedBuffer, atomEnvironment)
|
||||
state.foldsMarkerLayer = state.tokenizedBuffer.buffer.getMarkerLayer(state.foldsMarkerLayerId)
|
||||
state.config = atomEnvironment.config
|
||||
state.assert = atomEnvironment.assert
|
||||
state.grammarRegistry = atomEnvironment.grammars
|
||||
@@ -38,8 +42,8 @@ class DisplayBuffer extends Model
|
||||
super
|
||||
|
||||
{
|
||||
tabLength, @editorWidthInChars, @tokenizedBuffer, buffer, ignoreInvisibles,
|
||||
@largeFileMode, @config, @assert, @grammarRegistry, @packageManager
|
||||
tabLength, @editorWidthInChars, @tokenizedBuffer, @foldsMarkerLayer, buffer,
|
||||
ignoreInvisibles, @largeFileMode, @config, @assert, @grammarRegistry, @packageManager
|
||||
} = params
|
||||
|
||||
@emitter = new Emitter
|
||||
@@ -51,17 +55,22 @@ class DisplayBuffer extends Model
|
||||
})
|
||||
@buffer = @tokenizedBuffer.buffer
|
||||
@charWidthsByScope = {}
|
||||
@markers = {}
|
||||
@defaultMarkerLayer = new TextEditorMarkerLayer(this, @buffer.getDefaultMarkerLayer(), true)
|
||||
@customMarkerLayersById = {}
|
||||
@foldsByMarkerId = {}
|
||||
@decorationsById = {}
|
||||
@decorationsByMarkerId = {}
|
||||
@overlayDecorationsById = {}
|
||||
@layerDecorationsByMarkerLayerId = {}
|
||||
@decorationCountsByLayerId = {}
|
||||
@layerUpdateDisposablesByLayerId = {}
|
||||
|
||||
@disposables.add @tokenizedBuffer.observeGrammar @subscribeToScopedConfigSettings
|
||||
@disposables.add @tokenizedBuffer.onDidChange @handleTokenizedBufferChange
|
||||
@disposables.add @buffer.onDidCreateMarker @handleBufferMarkerCreated
|
||||
@disposables.add @buffer.onDidUpdateMarkers => @emitter.emit 'did-update-markers'
|
||||
@foldMarkerAttributes = Object.freeze({class: 'fold', displayBufferId: @id})
|
||||
folds = (new Fold(this, marker) for marker in @buffer.findMarkers(@getFoldMarkerAttributes()))
|
||||
@disposables.add @buffer.onDidCreateMarker @didCreateDefaultLayerMarker
|
||||
|
||||
@foldsMarkerLayer ?= @buffer.addMarkerLayer()
|
||||
folds = (new Fold(this, marker) for marker in @foldsMarkerLayer.getMarkers())
|
||||
@updateAllScreenLines()
|
||||
@decorateFold(fold) for fold in folds
|
||||
|
||||
@@ -107,17 +116,15 @@ class DisplayBuffer extends Model
|
||||
editorWidthInChars: @editorWidthInChars
|
||||
tokenizedBuffer: @tokenizedBuffer.serialize()
|
||||
largeFileMode: @largeFileMode
|
||||
foldsMarkerLayerId: @foldsMarkerLayer.id
|
||||
|
||||
copy: ->
|
||||
newDisplayBuffer = new DisplayBuffer({
|
||||
foldsMarkerLayer = @foldsMarkerLayer.copy()
|
||||
new DisplayBuffer({
|
||||
@buffer, tabLength: @getTabLength(), @largeFileMode, @config, @assert,
|
||||
@grammarRegistry, @packageManager
|
||||
@grammarRegistry, @packageManager, foldsMarkerLayer
|
||||
})
|
||||
|
||||
for marker in @findMarkers(displayBufferId: @id)
|
||||
marker.copy(displayBufferId: newDisplayBuffer.id)
|
||||
newDisplayBuffer
|
||||
|
||||
updateAllScreenLines: ->
|
||||
@maxLineLength = 0
|
||||
@screenLines = []
|
||||
@@ -158,6 +165,9 @@ class DisplayBuffer extends Model
|
||||
onDidUpdateMarkers: (callback) ->
|
||||
@emitter.on 'did-update-markers', callback
|
||||
|
||||
onDidUpdateDecorations: (callback) ->
|
||||
@emitter.on 'did-update-decorations', callback
|
||||
|
||||
emitDidChange: (eventProperties, refreshMarkers=true) ->
|
||||
@emitter.emit 'did-change', eventProperties
|
||||
if refreshMarkers
|
||||
@@ -177,6 +187,8 @@ class DisplayBuffer extends Model
|
||||
# visible - A {Boolean} indicating of the tokenized buffer is shown
|
||||
setVisible: (visible) -> @tokenizedBuffer.setVisible(visible)
|
||||
|
||||
setUpdatedSynchronously: (@updatedSynchronously) ->
|
||||
|
||||
getVerticalScrollMargin: ->
|
||||
maxScrollMargin = Math.floor(((@getHeight() / @getLineHeightInPixels()) - 1) / 2)
|
||||
Math.min(@verticalScrollMargin, maxScrollMargin)
|
||||
@@ -386,10 +398,14 @@ class DisplayBuffer extends Model
|
||||
# Returns the new {Fold}.
|
||||
createFold: (startRow, endRow) ->
|
||||
unless @largeFileMode
|
||||
foldMarker =
|
||||
@findFoldMarker({startRow, endRow}) ?
|
||||
@buffer.markRange([[startRow, 0], [endRow, Infinity]], @getFoldMarkerAttributes())
|
||||
@foldForMarker(foldMarker)
|
||||
if foldMarker = @findFoldMarker({startRow, endRow})
|
||||
@foldForMarker(foldMarker)
|
||||
else
|
||||
foldMarker = @foldsMarkerLayer.markRange([[startRow, 0], [endRow, Infinity]])
|
||||
fold = new Fold(this, foldMarker)
|
||||
fold.updateDisplayBuffer()
|
||||
@decorateFold(fold)
|
||||
fold
|
||||
|
||||
isFoldedAtBufferRow: (bufferRow) ->
|
||||
@largestFoldContainingBufferRow(bufferRow)?
|
||||
@@ -769,52 +785,68 @@ class DisplayBuffer extends Model
|
||||
decorationsByMarkerId[marker.id] = decorations
|
||||
decorationsByMarkerId
|
||||
|
||||
decorationsStateForScreenRowRange: (startScreenRow, endScreenRow) ->
|
||||
decorationsState = {}
|
||||
|
||||
for layerId of @decorationCountsByLayerId
|
||||
layer = @getMarkerLayer(layerId)
|
||||
|
||||
for marker in layer.findMarkers(intersectsScreenRowRange: [startScreenRow, endScreenRow]) when marker.isValid()
|
||||
screenRange = marker.getScreenRange()
|
||||
rangeIsReversed = marker.isReversed()
|
||||
|
||||
if decorations = @decorationsByMarkerId[marker.id]
|
||||
for decoration in decorations
|
||||
decorationsState[decoration.id] = {
|
||||
properties: decoration.properties
|
||||
screenRange, rangeIsReversed
|
||||
}
|
||||
|
||||
if layerDecorations = @layerDecorationsByMarkerLayerId[layerId]
|
||||
for layerDecoration in layerDecorations
|
||||
decorationsState["#{layerDecoration.id}-#{marker.id}"] = {
|
||||
properties: layerDecoration.overridePropertiesByMarkerId[marker.id] ? layerDecoration.properties
|
||||
screenRange, rangeIsReversed
|
||||
}
|
||||
|
||||
decorationsState
|
||||
|
||||
decorateMarker: (marker, decorationParams) ->
|
||||
marker = @getMarker(marker.id)
|
||||
marker = @getMarkerLayer(marker.layer.id).getMarker(marker.id)
|
||||
decoration = new Decoration(marker, this, decorationParams)
|
||||
decorationDestroyedDisposable = decoration.onDidDestroy =>
|
||||
@removeDecoration(decoration)
|
||||
@disposables.remove(decorationDestroyedDisposable)
|
||||
@disposables.add(decorationDestroyedDisposable)
|
||||
@decorationsByMarkerId[marker.id] ?= []
|
||||
@decorationsByMarkerId[marker.id].push(decoration)
|
||||
@overlayDecorationsById[decoration.id] = decoration if decoration.isType('overlay')
|
||||
@decorationsById[decoration.id] = decoration
|
||||
@observeDecoratedLayer(marker.layer)
|
||||
@scheduleUpdateDecorationsEvent()
|
||||
@emitter.emit 'did-add-decoration', decoration
|
||||
decoration
|
||||
|
||||
removeDecoration: (decoration) ->
|
||||
{marker} = decoration
|
||||
return unless decorations = @decorationsByMarkerId[marker.id]
|
||||
index = decorations.indexOf(decoration)
|
||||
|
||||
if index > -1
|
||||
decorations.splice(index, 1)
|
||||
delete @decorationsById[decoration.id]
|
||||
@emitter.emit 'did-remove-decoration', decoration
|
||||
delete @decorationsByMarkerId[marker.id] if decorations.length is 0
|
||||
delete @overlayDecorationsById[decoration.id]
|
||||
decorateMarkerLayer: (markerLayer, decorationParams) ->
|
||||
decoration = new LayerDecoration(markerLayer, this, decorationParams)
|
||||
@layerDecorationsByMarkerLayerId[markerLayer.id] ?= []
|
||||
@layerDecorationsByMarkerLayerId[markerLayer.id].push(decoration)
|
||||
@observeDecoratedLayer(markerLayer)
|
||||
@scheduleUpdateDecorationsEvent()
|
||||
decoration
|
||||
|
||||
decorationsForMarkerId: (markerId) ->
|
||||
@decorationsByMarkerId[markerId]
|
||||
|
||||
# Retrieves a {Marker} based on its id.
|
||||
# Retrieves a {TextEditorMarker} based on its id.
|
||||
#
|
||||
# id - A {Number} representing a marker id
|
||||
#
|
||||
# Returns the {Marker} (if it exists).
|
||||
# Returns the {TextEditorMarker} (if it exists).
|
||||
getMarker: (id) ->
|
||||
unless marker = @markers[id]
|
||||
if bufferMarker = @buffer.getMarker(id)
|
||||
marker = new Marker({bufferMarker, displayBuffer: this})
|
||||
@markers[id] = marker
|
||||
marker
|
||||
@defaultMarkerLayer.getMarker(id)
|
||||
|
||||
# Retrieves the active markers in the buffer.
|
||||
#
|
||||
# Returns an {Array} of existing {Marker}s.
|
||||
# Returns an {Array} of existing {TextEditorMarker}s.
|
||||
getMarkers: ->
|
||||
@buffer.getMarkers().map ({id}) => @getMarker(id)
|
||||
@defaultMarkerLayer.getMarkers()
|
||||
|
||||
getMarkerCount: ->
|
||||
@buffer.getMarkerCount()
|
||||
@@ -822,54 +854,46 @@ class DisplayBuffer extends Model
|
||||
# Public: Constructs a new marker at the given screen range.
|
||||
#
|
||||
# range - The marker {Range} (representing the distance between the head and tail)
|
||||
# options - Options to pass to the {Marker} constructor
|
||||
# options - Options to pass to the {TextEditorMarker} constructor
|
||||
#
|
||||
# Returns a {Number} representing the new marker's ID.
|
||||
markScreenRange: (args...) ->
|
||||
bufferRange = @bufferRangeForScreenRange(args.shift())
|
||||
@markBufferRange(bufferRange, args...)
|
||||
markScreenRange: (screenRange, options) ->
|
||||
@defaultMarkerLayer.markScreenRange(screenRange, options)
|
||||
|
||||
# Public: Constructs a new marker at the given buffer range.
|
||||
#
|
||||
# range - The marker {Range} (representing the distance between the head and tail)
|
||||
# options - Options to pass to the {Marker} constructor
|
||||
# options - Options to pass to the {TextEditorMarker} constructor
|
||||
#
|
||||
# Returns a {Number} representing the new marker's ID.
|
||||
markBufferRange: (range, options) ->
|
||||
@getMarker(@buffer.markRange(range, options).id)
|
||||
markBufferRange: (bufferRange, options) ->
|
||||
@defaultMarkerLayer.markBufferRange(bufferRange, options)
|
||||
|
||||
# Public: Constructs a new marker at the given screen position.
|
||||
#
|
||||
# range - The marker {Range} (representing the distance between the head and tail)
|
||||
# options - Options to pass to the {Marker} constructor
|
||||
# options - Options to pass to the {TextEditorMarker} constructor
|
||||
#
|
||||
# Returns a {Number} representing the new marker's ID.
|
||||
markScreenPosition: (screenPosition, options) ->
|
||||
@markBufferPosition(@bufferPositionForScreenPosition(screenPosition), options)
|
||||
@defaultMarkerLayer.markScreenPosition(screenPosition, options)
|
||||
|
||||
# Public: Constructs a new marker at the given buffer position.
|
||||
#
|
||||
# range - The marker {Range} (representing the distance between the head and tail)
|
||||
# options - Options to pass to the {Marker} constructor
|
||||
# options - Options to pass to the {TextEditorMarker} constructor
|
||||
#
|
||||
# Returns a {Number} representing the new marker's ID.
|
||||
markBufferPosition: (bufferPosition, options) ->
|
||||
@getMarker(@buffer.markPosition(bufferPosition, options).id)
|
||||
|
||||
# Public: Removes the marker with the given id.
|
||||
#
|
||||
# id - The {Number} of the ID to remove
|
||||
destroyMarker: (id) ->
|
||||
@buffer.destroyMarker(id)
|
||||
delete @markers[id]
|
||||
@defaultMarkerLayer.markBufferPosition(bufferPosition, options)
|
||||
|
||||
# Finds the first marker satisfying the given attributes
|
||||
#
|
||||
# Refer to {DisplayBuffer::findMarkers} for details.
|
||||
#
|
||||
# Returns a {Marker} or null
|
||||
# Returns a {TextEditorMarker} or null
|
||||
findMarker: (params) ->
|
||||
@findMarkers(params)[0]
|
||||
@defaultMarkerLayer.findMarkers(params)[0]
|
||||
|
||||
# Public: Find all markers satisfying a set of parameters.
|
||||
#
|
||||
@@ -888,69 +912,36 @@ class DisplayBuffer extends Model
|
||||
# :containedInBufferRange - A {Range} or range-compatible {Array}. Only
|
||||
# returns markers contained within this range.
|
||||
#
|
||||
# Returns an {Array} of {Marker}s
|
||||
# Returns an {Array} of {TextEditorMarker}s
|
||||
findMarkers: (params) ->
|
||||
params = @translateToBufferMarkerParams(params)
|
||||
@buffer.findMarkers(params).map (stringMarker) => @getMarker(stringMarker.id)
|
||||
@defaultMarkerLayer.findMarkers(params)
|
||||
|
||||
translateToBufferMarkerParams: (params) ->
|
||||
bufferMarkerParams = {}
|
||||
for key, value of params
|
||||
switch key
|
||||
when 'startBufferRow'
|
||||
key = 'startRow'
|
||||
when 'endBufferRow'
|
||||
key = 'endRow'
|
||||
when 'startScreenRow'
|
||||
key = 'startRow'
|
||||
value = @bufferRowForScreenRow(value)
|
||||
when 'endScreenRow'
|
||||
key = 'endRow'
|
||||
value = @bufferRowForScreenRow(value)
|
||||
when 'intersectsBufferRowRange'
|
||||
key = 'intersectsRowRange'
|
||||
when 'intersectsScreenRowRange'
|
||||
key = 'intersectsRowRange'
|
||||
[startRow, endRow] = value
|
||||
value = [@bufferRowForScreenRow(startRow), @bufferRowForScreenRow(endRow)]
|
||||
when 'containsBufferRange'
|
||||
key = 'containsRange'
|
||||
when 'containsBufferPosition'
|
||||
key = 'containsPosition'
|
||||
when 'containedInBufferRange'
|
||||
key = 'containedInRange'
|
||||
when 'containedInScreenRange'
|
||||
key = 'containedInRange'
|
||||
value = @bufferRangeForScreenRange(value)
|
||||
when 'intersectsBufferRange'
|
||||
key = 'intersectsRange'
|
||||
when 'intersectsScreenRange'
|
||||
key = 'intersectsRange'
|
||||
value = @bufferRangeForScreenRange(value)
|
||||
bufferMarkerParams[key] = value
|
||||
addMarkerLayer: (options) ->
|
||||
bufferLayer = @buffer.addMarkerLayer(options)
|
||||
@getMarkerLayer(bufferLayer.id)
|
||||
|
||||
bufferMarkerParams
|
||||
getMarkerLayer: (id) ->
|
||||
if layer = @customMarkerLayersById[id]
|
||||
layer
|
||||
else if bufferLayer = @buffer.getMarkerLayer(id)
|
||||
@customMarkerLayersById[id] = new TextEditorMarkerLayer(this, bufferLayer)
|
||||
|
||||
findFoldMarker: (attributes) ->
|
||||
@findFoldMarkers(attributes)[0]
|
||||
getDefaultMarkerLayer: -> @defaultMarkerLayer
|
||||
|
||||
findFoldMarkers: (attributes) ->
|
||||
@buffer.findMarkers(@getFoldMarkerAttributes(attributes))
|
||||
findFoldMarker: (params) ->
|
||||
@findFoldMarkers(params)[0]
|
||||
|
||||
getFoldMarkerAttributes: (attributes) ->
|
||||
if attributes
|
||||
_.extend(attributes, @foldMarkerAttributes)
|
||||
else
|
||||
@foldMarkerAttributes
|
||||
findFoldMarkers: (params) ->
|
||||
@foldsMarkerLayer.findMarkers(params)
|
||||
|
||||
refreshMarkerScreenPositions: ->
|
||||
for marker in @getMarkers()
|
||||
marker.notifyObservers(textChanged: false)
|
||||
@defaultMarkerLayer.refreshMarkerScreenPositions()
|
||||
layer.refreshMarkerScreenPositions() for id, layer of @customMarkerLayersById
|
||||
return
|
||||
|
||||
destroyed: ->
|
||||
fold.destroy() for markerId, fold of @foldsByMarkerId
|
||||
marker.disposables.dispose() for id, marker of @markers
|
||||
@defaultMarkerLayer.destroy()
|
||||
@foldsMarkerLayer.destroy()
|
||||
@scopedConfigSubscriptions.dispose()
|
||||
@disposables.dispose()
|
||||
@tokenizedBuffer.destroy()
|
||||
@@ -1072,17 +1063,23 @@ class DisplayBuffer extends Model
|
||||
@longestScreenRow = screenRow
|
||||
@maxLineLength = length
|
||||
|
||||
handleBufferMarkerCreated: (textBufferMarker) =>
|
||||
if textBufferMarker.matchesParams(@getFoldMarkerAttributes())
|
||||
fold = new Fold(this, textBufferMarker)
|
||||
fold.updateDisplayBuffer()
|
||||
@decorateFold(fold)
|
||||
|
||||
didCreateDefaultLayerMarker: (textBufferMarker) =>
|
||||
if marker = @getMarker(textBufferMarker.id)
|
||||
# The marker might have been removed in some other handler called before
|
||||
# this one. Only emit when the marker still exists.
|
||||
@emitter.emit 'did-create-marker', marker
|
||||
|
||||
scheduleUpdateDecorationsEvent: ->
|
||||
if @updatedSynchronously
|
||||
@emitter.emit 'did-update-decorations'
|
||||
return
|
||||
|
||||
unless @didUpdateDecorationsEventScheduled
|
||||
@didUpdateDecorationsEventScheduled = true
|
||||
process.nextTick =>
|
||||
@didUpdateDecorationsEventScheduled = false
|
||||
@emitter.emit 'did-update-decorations'
|
||||
|
||||
decorateFold: (fold) ->
|
||||
@decorateMarker(fold.marker, type: 'line-number', class: 'folded')
|
||||
|
||||
@@ -1095,6 +1092,42 @@ class DisplayBuffer extends Model
|
||||
else
|
||||
delete @overlayDecorationsById[decoration.id]
|
||||
|
||||
didDestroyDecoration: (decoration) ->
|
||||
{marker} = decoration
|
||||
return unless decorations = @decorationsByMarkerId[marker.id]
|
||||
index = decorations.indexOf(decoration)
|
||||
|
||||
if index > -1
|
||||
decorations.splice(index, 1)
|
||||
delete @decorationsById[decoration.id]
|
||||
@emitter.emit 'did-remove-decoration', decoration
|
||||
delete @decorationsByMarkerId[marker.id] if decorations.length is 0
|
||||
delete @overlayDecorationsById[decoration.id]
|
||||
@unobserveDecoratedLayer(marker.layer)
|
||||
@scheduleUpdateDecorationsEvent()
|
||||
|
||||
didDestroyLayerDecoration: (decoration) ->
|
||||
{markerLayer} = decoration
|
||||
return unless decorations = @layerDecorationsByMarkerLayerId[markerLayer.id]
|
||||
index = decorations.indexOf(decoration)
|
||||
|
||||
if index > -1
|
||||
decorations.splice(index, 1)
|
||||
delete @layerDecorationsByMarkerLayerId[markerLayer.id] if decorations.length is 0
|
||||
@unobserveDecoratedLayer(markerLayer)
|
||||
@scheduleUpdateDecorationsEvent()
|
||||
|
||||
observeDecoratedLayer: (layer) ->
|
||||
@decorationCountsByLayerId[layer.id] ?= 0
|
||||
if ++@decorationCountsByLayerId[layer.id] is 1
|
||||
@layerUpdateDisposablesByLayerId[layer.id] = layer.onDidUpdate(@scheduleUpdateDecorationsEvent.bind(this))
|
||||
|
||||
unobserveDecoratedLayer: (layer) ->
|
||||
if --@decorationCountsByLayerId[layer.id] is 0
|
||||
@layerUpdateDisposablesByLayerId[layer.id].dispose()
|
||||
delete @decorationCountsByLayerId[layer.id]
|
||||
delete @layerUpdateDisposablesByLayerId[layer.id]
|
||||
|
||||
checkScreenLinesInvariant: ->
|
||||
return if @isSoftWrapped()
|
||||
return if _.size(@foldsByMarkerId) > 0
|
||||
|
||||
@@ -71,13 +71,13 @@ class Gutter
|
||||
isVisible: ->
|
||||
@visible
|
||||
|
||||
# Essential: Add a decoration that tracks a {Marker}. When the marker moves,
|
||||
# Essential: Add a decoration that tracks a {TextEditorMarker}. When the marker moves,
|
||||
# is invalidated, or is destroyed, the decoration will be updated to reflect
|
||||
# the marker's state.
|
||||
#
|
||||
# ## Arguments
|
||||
#
|
||||
# * `marker` A {Marker} you want this decoration to follow.
|
||||
# * `marker` A {TextEditorMarker} you want this decoration to follow.
|
||||
# * `decorationParams` An {Object} representing the decoration. It is passed
|
||||
# to {TextEditor::decorateMarker} as its `decorationParams` and so supports
|
||||
# all options documented there.
|
||||
|
||||
@@ -57,6 +57,12 @@ module.exports = ({blobStore}) ->
|
||||
|
||||
document.title = "Spec Suite"
|
||||
|
||||
# Avoid throttling of test window by playing silence
|
||||
context = new AudioContext()
|
||||
source = context.createBufferSource()
|
||||
source.connect(context.destination)
|
||||
source.start(0)
|
||||
|
||||
testRunner = require(testRunnerPath)
|
||||
legacyTestRunner = require(legacyTestRunnerPath)
|
||||
buildDefaultApplicationDelegate = -> new ApplicationDelegate()
|
||||
|
||||
61
src/layer-decoration.coffee
Normal file
61
src/layer-decoration.coffee
Normal file
@@ -0,0 +1,61 @@
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
idCounter = 0
|
||||
nextId = -> idCounter++
|
||||
|
||||
# Essential: Represents a decoration that applies to every marker on a given
|
||||
# layer. Created via {TextEditor::decorateMarkerLayer}.
|
||||
module.exports =
|
||||
class LayerDecoration
|
||||
constructor: (@markerLayer, @displayBuffer, @properties) ->
|
||||
@id = nextId()
|
||||
@destroyed = false
|
||||
@markerLayerDestroyedDisposable = @markerLayer.onDidDestroy => @destroy()
|
||||
@overridePropertiesByMarkerId = {}
|
||||
|
||||
# Essential: Destroys the decoration.
|
||||
destroy: ->
|
||||
return if @destroyed
|
||||
@markerLayerDestroyedDisposable.dispose()
|
||||
@markerLayerDestroyedDisposable = null
|
||||
@destroyed = true
|
||||
@displayBuffer.didDestroyLayerDecoration(this)
|
||||
|
||||
# Essential: Determine whether this decoration is destroyed.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isDestroyed: -> @destroyed
|
||||
|
||||
getId: -> @id
|
||||
|
||||
getMarkerLayer: -> @markerLayer
|
||||
|
||||
# Essential: Get this decoration's properties.
|
||||
#
|
||||
# Returns an {Object}.
|
||||
getProperties: ->
|
||||
@properties
|
||||
|
||||
# Essential: Set this decoration's properties.
|
||||
#
|
||||
# * `newProperties` See {TextEditor::decorateMarker} for more information on
|
||||
# the properties. The `type` of `gutter` and `overlay` are not supported on
|
||||
# layer decorations.
|
||||
setProperties: (newProperties) ->
|
||||
return if @destroyed
|
||||
@properties = newProperties
|
||||
@displayBuffer.scheduleUpdateDecorationsEvent()
|
||||
|
||||
# Essential: Override the decoration properties for a specific marker.
|
||||
#
|
||||
# * `marker` The {TextEditorMarker} or {Marker} for which to override
|
||||
# properties.
|
||||
# * `properties` An {Object} containing properties to apply to this marker.
|
||||
# Pass `null` to clear the override.
|
||||
setPropertiesForMarker: (marker, properties) ->
|
||||
return if @destroyed
|
||||
if properties?
|
||||
@overridePropertiesByMarkerId[marker.id] = properties
|
||||
else
|
||||
delete @overridePropertiesByMarkerId[marker.id]
|
||||
@displayBuffer.scheduleUpdateDecorationsEvent()
|
||||
@@ -46,7 +46,7 @@ normalizeLabel = (label) ->
|
||||
label.replace(/\&/g, '')
|
||||
|
||||
cloneMenuItem = (item) ->
|
||||
item = _.pick(item, 'type', 'label', 'enabled', 'visible', 'command', 'submenu', 'commandDetail')
|
||||
item = _.pick(item, 'type', 'label', 'enabled', 'visible', 'command', 'submenu', 'commandDetail', 'role')
|
||||
if item.submenu?
|
||||
item.submenu = item.submenu.map (submenuItem) -> cloneMenuItem(submenuItem)
|
||||
item
|
||||
|
||||
@@ -336,8 +336,10 @@ class PackageManager
|
||||
keymapsToEnable = _.difference(oldValue, newValue)
|
||||
keymapsToDisable = _.difference(newValue, oldValue)
|
||||
|
||||
@getLoadedPackage(packageName).deactivateKeymaps() for packageName in keymapsToDisable when not @isPackageDisabled(packageName)
|
||||
@getLoadedPackage(packageName).activateKeymaps() for packageName in keymapsToEnable when not @isPackageDisabled(packageName)
|
||||
for packageName in keymapsToDisable when not @isPackageDisabled(packageName)
|
||||
@getLoadedPackage(packageName)?.deactivateKeymaps()
|
||||
for packageName in keymapsToEnable when not @isPackageDisabled(packageName)
|
||||
@getLoadedPackage(packageName)?.activateKeymaps()
|
||||
null
|
||||
|
||||
loadPackages: ->
|
||||
@@ -419,7 +421,7 @@ class PackageManager
|
||||
@config.transact =>
|
||||
for pack in packages
|
||||
promise = @activatePackage(pack.name)
|
||||
promises.push(promise) unless pack.hasActivationCommands()
|
||||
promises.push(promise) unless pack.activationShouldBeDeferred()
|
||||
return
|
||||
@observeDisabledPackages()
|
||||
@observePackagesWithKeymapsDisabled()
|
||||
|
||||
@@ -722,7 +722,7 @@ class Pane extends Model
|
||||
@notificationManager.addWarning("Unable to save file: #{error.message}")
|
||||
else if error.code is 'EACCES'
|
||||
addWarningWithPath('Unable to save file: Permission denied')
|
||||
else if error.code in ['EPERM', 'EBUSY', 'UNKNOWN', 'EEXIST']
|
||||
else if error.code in ['EPERM', 'EBUSY', 'UNKNOWN', 'EEXIST', 'ELOOP']
|
||||
addWarningWithPath('Unable to save file', detail: error.message)
|
||||
else if error.code is 'EROFS'
|
||||
addWarningWithPath('Unable to save file: Read-only file system')
|
||||
|
||||
@@ -216,7 +216,7 @@ class TextEditorComponent
|
||||
@updatesPaused = false
|
||||
if @updateRequestedWhilePaused and @canUpdate()
|
||||
@updateRequestedWhilePaused = false
|
||||
@updateSync()
|
||||
@requestUpdate()
|
||||
|
||||
getTopmostDOMNode: ->
|
||||
@hostElement
|
||||
|
||||
@@ -103,6 +103,7 @@ class TextEditorElement extends HTMLElement
|
||||
return if model.isDestroyed()
|
||||
|
||||
@model = model
|
||||
@model.setUpdatedSynchronously(@isUpdatedSynchronously())
|
||||
@initializeContent()
|
||||
@mountComponent()
|
||||
@addGrammarScopeAttribute()
|
||||
@@ -194,7 +195,9 @@ class TextEditorElement extends HTMLElement
|
||||
hasFocus: ->
|
||||
this is document.activeElement or @contains(document.activeElement)
|
||||
|
||||
setUpdatedSynchronously: (@updatedSynchronously) -> @updatedSynchronously
|
||||
setUpdatedSynchronously: (@updatedSynchronously) ->
|
||||
@model?.setUpdatedSynchronously(@updatedSynchronously)
|
||||
@updatedSynchronously
|
||||
|
||||
isUpdatedSynchronously: -> @updatedSynchronously
|
||||
|
||||
|
||||
192
src/text-editor-marker-layer.coffee
Normal file
192
src/text-editor-marker-layer.coffee
Normal file
@@ -0,0 +1,192 @@
|
||||
TextEditorMarker = require './text-editor-marker'
|
||||
|
||||
# Public: *Experimental:* A container for a related set of markers at the
|
||||
# {TextEditor} level. Wraps an underlying {MarkerLayer} on the editor's
|
||||
# {TextBuffer}.
|
||||
#
|
||||
# This API is experimental and subject to change on any release.
|
||||
module.exports =
|
||||
class TextEditorMarkerLayer
|
||||
constructor: (@displayBuffer, @bufferMarkerLayer, @isDefaultLayer) ->
|
||||
@id = @bufferMarkerLayer.id
|
||||
@markersById = {}
|
||||
|
||||
###
|
||||
Section: Lifecycle
|
||||
###
|
||||
|
||||
# Essential: Destroy this layer.
|
||||
destroy: ->
|
||||
if @isDefaultLayer
|
||||
marker.destroy() for id, marker of @markersById
|
||||
else
|
||||
@bufferMarkerLayer.destroy()
|
||||
|
||||
###
|
||||
Section: Querying
|
||||
###
|
||||
|
||||
# Essential: Get an existing marker by its id.
|
||||
#
|
||||
# Returns a {TextEditorMarker}.
|
||||
getMarker: (id) ->
|
||||
if editorMarker = @markersById[id]
|
||||
editorMarker
|
||||
else if bufferMarker = @bufferMarkerLayer.getMarker(id)
|
||||
@markersById[id] = new TextEditorMarker(this, bufferMarker)
|
||||
|
||||
# Essential: Get all markers in the layer.
|
||||
#
|
||||
# Returns an {Array} of {TextEditorMarker}s.
|
||||
getMarkers: ->
|
||||
@bufferMarkerLayer.getMarkers().map ({id}) => @getMarker(id)
|
||||
|
||||
# Public: Get the number of markers in the marker layer.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getMarkerCount: ->
|
||||
@bufferMarkerLayer.getMarkerCount()
|
||||
|
||||
# Public: Find markers in the layer conforming to the given parameters.
|
||||
#
|
||||
# See the documentation for {TextEditor::findMarkers}.
|
||||
findMarkers: (params) ->
|
||||
params = @translateToBufferMarkerParams(params)
|
||||
@bufferMarkerLayer.findMarkers(params).map (stringMarker) => @getMarker(stringMarker.id)
|
||||
|
||||
###
|
||||
Section: Marker creation
|
||||
###
|
||||
|
||||
# Essential: Create a marker on this layer with the given range in buffer
|
||||
# coordinates.
|
||||
#
|
||||
# See the documentation for {TextEditor::markBufferRange}
|
||||
markBufferRange: (bufferRange, options) ->
|
||||
@getMarker(@bufferMarkerLayer.markRange(bufferRange, options).id)
|
||||
|
||||
# Essential: Create a marker on this layer with the given range in screen
|
||||
# coordinates.
|
||||
#
|
||||
# See the documentation for {TextEditor::markScreenRange}
|
||||
markScreenRange: (screenRange, options) ->
|
||||
bufferRange = @displayBuffer.bufferRangeForScreenRange(screenRange)
|
||||
@markBufferRange(bufferRange, options)
|
||||
|
||||
# Public: Create a marker on this layer with the given buffer position and no
|
||||
# tail.
|
||||
#
|
||||
# See the documentation for {TextEditor::markBufferPosition}
|
||||
markBufferPosition: (bufferPosition, options) ->
|
||||
@getMarker(@bufferMarkerLayer.markPosition(bufferPosition, options).id)
|
||||
|
||||
# Public: Create a marker on this layer with the given screen position and no
|
||||
# tail.
|
||||
#
|
||||
# See the documentation for {TextEditor::markScreenPosition}
|
||||
markScreenPosition: (screenPosition, options) ->
|
||||
bufferPosition = @displayBuffer.bufferPositionForScreenPosition(screenPosition)
|
||||
@markBufferPosition(bufferPosition, options)
|
||||
|
||||
###
|
||||
Section: Event Subscription
|
||||
###
|
||||
|
||||
# Public: Subscribe to be notified asynchronously whenever markers are
|
||||
# created, updated, or destroyed on this layer. *Prefer this method for
|
||||
# optimal performance when interacting with layers that could contain large
|
||||
# numbers of markers.*
|
||||
#
|
||||
# * `callback` A {Function} that will be called with no arguments when changes
|
||||
# occur on this layer.
|
||||
#
|
||||
# Subscribers are notified once, asynchronously when any number of changes
|
||||
# occur in a given tick of the event loop. You should re-query the layer
|
||||
# to determine the state of markers in which you're interested in. It may
|
||||
# be counter-intuitive, but this is much more efficient than subscribing to
|
||||
# events on individual markers, which are expensive to deliver.
|
||||
#
|
||||
# Returns a {Disposable}.
|
||||
onDidUpdate: (callback) ->
|
||||
@bufferMarkerLayer.onDidUpdate(callback)
|
||||
|
||||
# Public: Subscribe to be notified synchronously whenever markers are created
|
||||
# on this layer. *Avoid this method for optimal performance when interacting
|
||||
# with layers that could contain large numbers of markers.*
|
||||
#
|
||||
# * `callback` A {Function} that will be called with a {TextEditorMarker}
|
||||
# whenever a new marker is created.
|
||||
#
|
||||
# You should prefer {onDidUpdate} when synchronous notifications aren't
|
||||
# absolutely necessary.
|
||||
#
|
||||
# Returns a {Disposable}.
|
||||
onDidCreateMarker: (callback) ->
|
||||
@bufferMarkerLayer.onDidCreateMarker (bufferMarker) =>
|
||||
callback(@getMarker(bufferMarker.id))
|
||||
|
||||
# Public: Subscribe to be notified synchronously when this layer is destroyed.
|
||||
#
|
||||
# Returns a {Disposable}.
|
||||
onDidDestroy: (callback) ->
|
||||
@bufferMarkerLayer.onDidDestroy(callback)
|
||||
|
||||
###
|
||||
Section: Private
|
||||
###
|
||||
|
||||
refreshMarkerScreenPositions: ->
|
||||
for marker in @getMarkers()
|
||||
marker.notifyObservers(textChanged: false)
|
||||
return
|
||||
|
||||
didDestroyMarker: (marker) ->
|
||||
delete @markersById[marker.id]
|
||||
|
||||
translateToBufferMarkerParams: (params) ->
|
||||
bufferMarkerParams = {}
|
||||
for key, value of params
|
||||
switch key
|
||||
when 'startBufferPosition'
|
||||
key = 'startPosition'
|
||||
when 'endBufferPosition'
|
||||
key = 'endPosition'
|
||||
when 'startScreenPosition'
|
||||
key = 'startPosition'
|
||||
value = @displayBuffer.bufferPositionForScreenPosition(value)
|
||||
when 'endScreenPosition'
|
||||
key = 'endPosition'
|
||||
value = @displayBuffer.bufferPositionForScreenPosition(value)
|
||||
when 'startBufferRow'
|
||||
key = 'startRow'
|
||||
when 'endBufferRow'
|
||||
key = 'endRow'
|
||||
when 'startScreenRow'
|
||||
key = 'startRow'
|
||||
value = @displayBuffer.bufferRowForScreenRow(value)
|
||||
when 'endScreenRow'
|
||||
key = 'endRow'
|
||||
value = @displayBuffer.bufferRowForScreenRow(value)
|
||||
when 'intersectsBufferRowRange'
|
||||
key = 'intersectsRowRange'
|
||||
when 'intersectsScreenRowRange'
|
||||
key = 'intersectsRowRange'
|
||||
[startRow, endRow] = value
|
||||
value = [@displayBuffer.bufferRowForScreenRow(startRow), @displayBuffer.bufferRowForScreenRow(endRow)]
|
||||
when 'containsBufferRange'
|
||||
key = 'containsRange'
|
||||
when 'containsBufferPosition'
|
||||
key = 'containsPosition'
|
||||
when 'containedInBufferRange'
|
||||
key = 'containedInRange'
|
||||
when 'containedInScreenRange'
|
||||
key = 'containedInRange'
|
||||
value = @displayBuffer.bufferRangeForScreenRange(value)
|
||||
when 'intersectsBufferRange'
|
||||
key = 'intersectsRange'
|
||||
when 'intersectsScreenRange'
|
||||
key = 'intersectsRange'
|
||||
value = @displayBuffer.bufferRangeForScreenRange(value)
|
||||
bufferMarkerParams[key] = value
|
||||
|
||||
bufferMarkerParams
|
||||
@@ -6,7 +6,7 @@ _ = require 'underscore-plus'
|
||||
# targets, misspelled words, and anything else that needs to track a logical
|
||||
# location in the buffer over time.
|
||||
#
|
||||
# ### Marker Creation
|
||||
# ### TextEditorMarker Creation
|
||||
#
|
||||
# Use {TextEditor::markBufferRange} rather than creating Markers directly.
|
||||
#
|
||||
@@ -40,7 +40,7 @@ _ = require 'underscore-plus'
|
||||
#
|
||||
# See {TextEditor::markBufferRange} for usage.
|
||||
module.exports =
|
||||
class Marker
|
||||
class TextEditorMarker
|
||||
bufferMarkerSubscription: null
|
||||
oldHeadBufferPosition: null
|
||||
oldHeadScreenPosition: null
|
||||
@@ -53,7 +53,8 @@ class Marker
|
||||
Section: Construction and Destruction
|
||||
###
|
||||
|
||||
constructor: ({@bufferMarker, @displayBuffer}) ->
|
||||
constructor: (@layer, @bufferMarker) ->
|
||||
{@displayBuffer} = @layer
|
||||
@emitter = new Emitter
|
||||
@disposables = new CompositeDisposable
|
||||
@id = @bufferMarker.id
|
||||
@@ -66,7 +67,7 @@ class Marker
|
||||
@bufferMarker.destroy()
|
||||
@disposables.dispose()
|
||||
|
||||
# Essential: Creates and returns a new {Marker} with the same properties as
|
||||
# Essential: Creates and returns a new {TextEditorMarker} with the same properties as
|
||||
# this marker.
|
||||
#
|
||||
# {Selection} markers (markers with a custom property `type: "selection"`)
|
||||
@@ -79,9 +80,9 @@ class Marker
|
||||
# marker. The new marker's properties are computed by extending this marker's
|
||||
# properties with `properties`.
|
||||
#
|
||||
# Returns a {Marker}.
|
||||
# Returns a {TextEditorMarker}.
|
||||
copy: (properties) ->
|
||||
@displayBuffer.getMarker(@bufferMarker.copy(properties).id)
|
||||
@layer.getMarker(@bufferMarker.copy(properties).id)
|
||||
|
||||
###
|
||||
Section: Event Subscription
|
||||
@@ -129,7 +130,7 @@ class Marker
|
||||
@emitter.on 'did-destroy', callback
|
||||
|
||||
###
|
||||
Section: Marker Details
|
||||
Section: TextEditorMarker Details
|
||||
###
|
||||
|
||||
# Essential: Returns a {Boolean} indicating whether the marker is valid. Markers can be
|
||||
@@ -140,7 +141,7 @@ class Marker
|
||||
# Essential: Returns a {Boolean} indicating whether the marker has been destroyed. A marker
|
||||
# can be invalid without being destroyed, in which case undoing the invalidating
|
||||
# operation would restore the marker. Once a marker is destroyed by calling
|
||||
# {Marker::destroy}, no undo/redo operation can ever bring it back.
|
||||
# {TextEditorMarker::destroy}, no undo/redo operation can ever bring it back.
|
||||
isDestroyed: ->
|
||||
@bufferMarker.isDestroyed()
|
||||
|
||||
@@ -169,7 +170,7 @@ class Marker
|
||||
@bufferMarker.setProperties(properties)
|
||||
|
||||
matchesProperties: (attributes) ->
|
||||
attributes = @displayBuffer.translateToBufferMarkerParams(attributes)
|
||||
attributes = @layer.translateToBufferMarkerParams(attributes)
|
||||
@bufferMarker.matchesParams(attributes)
|
||||
|
||||
###
|
||||
@@ -179,14 +180,14 @@ class Marker
|
||||
# Essential: Returns a {Boolean} indicating whether this marker is equivalent to
|
||||
# another marker, meaning they have the same range and options.
|
||||
#
|
||||
# * `other` {Marker} other marker
|
||||
# * `other` {TextEditorMarker} other marker
|
||||
isEqual: (other) ->
|
||||
return false unless other instanceof @constructor
|
||||
@bufferMarker.isEqual(other.bufferMarker)
|
||||
|
||||
# Essential: Compares this marker to another based on their ranges.
|
||||
#
|
||||
# * `other` {Marker}
|
||||
# * `other` {TextEditorMarker}
|
||||
#
|
||||
# Returns a {Number}
|
||||
compare: (other) ->
|
||||
@@ -225,28 +226,28 @@ class Marker
|
||||
@setBufferRange(@displayBuffer.bufferRangeForScreenRange(screenRange), options)
|
||||
|
||||
# Essential: Retrieves the buffer position of the marker's start. This will always be
|
||||
# less than or equal to the result of {Marker::getEndBufferPosition}.
|
||||
# less than or equal to the result of {TextEditorMarker::getEndBufferPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getStartBufferPosition: ->
|
||||
@bufferMarker.getStartPosition()
|
||||
|
||||
# Essential: Retrieves the screen position of the marker's start. This will always be
|
||||
# less than or equal to the result of {Marker::getEndScreenPosition}.
|
||||
# less than or equal to the result of {TextEditorMarker::getEndScreenPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getStartScreenPosition: ->
|
||||
@displayBuffer.screenPositionForBufferPosition(@getStartBufferPosition(), wrapAtSoftNewlines: true)
|
||||
|
||||
# Essential: Retrieves the buffer position of the marker's end. This will always be
|
||||
# greater than or equal to the result of {Marker::getStartBufferPosition}.
|
||||
# greater than or equal to the result of {TextEditorMarker::getStartBufferPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getEndBufferPosition: ->
|
||||
@bufferMarker.getEndPosition()
|
||||
|
||||
# Essential: Retrieves the screen position of the marker's end. This will always be
|
||||
# greater than or equal to the result of {Marker::getStartScreenPosition}.
|
||||
# greater than or equal to the result of {TextEditorMarker::getStartScreenPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getEndScreenPosition: ->
|
||||
@@ -330,10 +331,10 @@ class Marker
|
||||
|
||||
# Returns a {String} representation of the marker
|
||||
inspect: ->
|
||||
"Marker(id: #{@id}, bufferRange: #{@getBufferRange()})"
|
||||
"TextEditorMarker(id: #{@id}, bufferRange: #{@getBufferRange()})"
|
||||
|
||||
destroyed: ->
|
||||
delete @displayBuffer.markers[@id]
|
||||
@layer.didDestroyMarker(this)
|
||||
@emitter.emit 'did-destroy'
|
||||
@emitter.dispose()
|
||||
|
||||
@@ -25,10 +25,9 @@ class TextEditorPresenter
|
||||
@emitter = new Emitter
|
||||
@visibleHighlights = {}
|
||||
@characterWidthsByScope = {}
|
||||
@rangesByDecorationId = {}
|
||||
@lineDecorationsByScreenRow = {}
|
||||
@lineNumberDecorationsByScreenRow = {}
|
||||
@customGutterDecorationsByGutterNameAndScreenRow = {}
|
||||
@customGutterDecorationsByGutterName = {}
|
||||
@screenRowsToMeasure = []
|
||||
@transferMeasurementsToModel()
|
||||
@transferMeasurementsFromModel()
|
||||
@@ -46,6 +45,9 @@ class TextEditorPresenter
|
||||
|
||||
destroy: ->
|
||||
@disposables.dispose()
|
||||
clearTimeout(@stoppedScrollingTimeoutId) if @stoppedScrollingTimeoutId?
|
||||
clearInterval(@reflowingInterval) if @reflowingInterval?
|
||||
@stopBlinkingCursors()
|
||||
|
||||
# Calls your `callback` when some changes in the model occurred and the current state has been updated.
|
||||
onDidUpdateState: (callback) ->
|
||||
@@ -181,7 +183,7 @@ class TextEditorPresenter
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
@disposables.add @model.onDidUpdateMarkers =>
|
||||
@disposables.add @model.onDidUpdateDecorations =>
|
||||
@shouldUpdateLinesState = true
|
||||
@shouldUpdateLineNumbersState = true
|
||||
@shouldUpdateDecorations = true
|
||||
@@ -210,11 +212,9 @@ class TextEditorPresenter
|
||||
@shouldUpdateGutterOrderState = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
@disposables.add @model.onDidAddDecoration(@didAddDecoration.bind(this))
|
||||
@disposables.add @model.onDidAddCursor(@didAddCursor.bind(this))
|
||||
@disposables.add @model.onDidRequestAutoscroll(@requestAutoscroll.bind(this))
|
||||
@disposables.add @model.onDidChangeFirstVisibleScreenRow(@didChangeFirstVisibleScreenRow.bind(this))
|
||||
@observeDecoration(decoration) for decoration in @model.getDecorations()
|
||||
@observeCursor(cursor) for cursor in @model.getCursors()
|
||||
@disposables.add @model.onDidAddGutter(@didAddGutter.bind(this))
|
||||
return
|
||||
@@ -623,16 +623,14 @@ class TextEditorPresenter
|
||||
@clearDecorationsForCustomGutterName(gutterName)
|
||||
else
|
||||
@customGutterDecorations[gutterName] = {}
|
||||
continue if not @gutterIsVisible(gutter)
|
||||
|
||||
relevantDecorations = @customGutterDecorationsInRange(gutterName, @startRow, @endRow - 1)
|
||||
relevantDecorations.forEach (decoration) =>
|
||||
decorationRange = decoration.getMarker().getScreenRange()
|
||||
@customGutterDecorations[gutterName][decoration.id] =
|
||||
top: @lineHeight * decorationRange.start.row
|
||||
height: @lineHeight * decorationRange.getRowCount()
|
||||
item: decoration.getProperties().item
|
||||
class: decoration.getProperties().class
|
||||
continue unless @gutterIsVisible(gutter)
|
||||
for decorationId, {properties, screenRange} of @customGutterDecorationsByGutterName[gutterName]
|
||||
@customGutterDecorations[gutterName][decorationId] =
|
||||
top: @lineHeight * screenRange.start.row
|
||||
height: @lineHeight * screenRange.getRowCount()
|
||||
item: properties.item
|
||||
class: properties.class
|
||||
|
||||
clearAllCustomGutterDecorations: ->
|
||||
allGutterNames = Object.keys(@customGutterDecorations)
|
||||
@@ -845,32 +843,20 @@ class TextEditorPresenter
|
||||
return null if @model.isMini()
|
||||
|
||||
decorationClasses = null
|
||||
for id, decoration of @lineDecorationsByScreenRow[row]
|
||||
for id, properties of @lineDecorationsByScreenRow[row]
|
||||
decorationClasses ?= []
|
||||
decorationClasses.push(decoration.getProperties().class)
|
||||
decorationClasses.push(properties.class)
|
||||
decorationClasses
|
||||
|
||||
lineNumberDecorationClassesForRow: (row) ->
|
||||
return null if @model.isMini()
|
||||
|
||||
decorationClasses = null
|
||||
for id, decoration of @lineNumberDecorationsByScreenRow[row]
|
||||
for id, properties of @lineNumberDecorationsByScreenRow[row]
|
||||
decorationClasses ?= []
|
||||
decorationClasses.push(decoration.getProperties().class)
|
||||
decorationClasses.push(properties.class)
|
||||
decorationClasses
|
||||
|
||||
# Returns a {Set} of {Decoration}s on the given custom gutter from startRow to endRow (inclusive).
|
||||
customGutterDecorationsInRange: (gutterName, startRow, endRow) ->
|
||||
decorations = new Set
|
||||
|
||||
return decorations if @model.isMini() or gutterName is 'line-number' or
|
||||
not @customGutterDecorationsByGutterNameAndScreenRow[gutterName]
|
||||
|
||||
for screenRow in [@startRow..@endRow - 1]
|
||||
for id, decoration of @customGutterDecorationsByGutterNameAndScreenRow[gutterName][screenRow]
|
||||
decorations.add(decoration)
|
||||
decorations
|
||||
|
||||
getCursorBlinkPeriod: -> @cursorBlinkPeriod
|
||||
|
||||
getCursorBlinkResumeDelay: -> @cursorBlinkResumeDelay
|
||||
@@ -1182,93 +1168,32 @@ class TextEditorPresenter
|
||||
|
||||
rect
|
||||
|
||||
observeDecoration: (decoration) ->
|
||||
decorationDisposables = new CompositeDisposable
|
||||
if decoration.isType('highlight')
|
||||
decorationDisposables.add decoration.onDidFlash =>
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
decorationDisposables.add decoration.onDidChangeProperties (event) =>
|
||||
@decorationPropertiesDidChange(decoration, event)
|
||||
decorationDisposables.add decoration.onDidDestroy =>
|
||||
@disposables.remove(decorationDisposables)
|
||||
decorationDisposables.dispose()
|
||||
@didDestroyDecoration(decoration)
|
||||
@disposables.add(decorationDisposables)
|
||||
|
||||
decorationPropertiesDidChange: (decoration, {oldProperties}) ->
|
||||
@shouldUpdateDecorations = true
|
||||
if decoration.isType('line') or decoration.isType('gutter')
|
||||
if decoration.isType('line') or Decoration.isType(oldProperties, 'line')
|
||||
@shouldUpdateLinesState = true
|
||||
if decoration.isType('line-number') or Decoration.isType(oldProperties, 'line-number')
|
||||
@shouldUpdateLineNumbersState = true
|
||||
if (decoration.isType('gutter') and not decoration.isType('line-number')) or
|
||||
(Decoration.isType(oldProperties, 'gutter') and not Decoration.isType(oldProperties, 'line-number'))
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
else if decoration.isType('overlay')
|
||||
@shouldUpdateOverlaysState = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
didDestroyDecoration: (decoration) ->
|
||||
@shouldUpdateDecorations = true
|
||||
if decoration.isType('line') or decoration.isType('gutter')
|
||||
@shouldUpdateLinesState = true if decoration.isType('line')
|
||||
if decoration.isType('line-number')
|
||||
@shouldUpdateLineNumbersState = true
|
||||
else if decoration.isType('gutter')
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
if decoration.isType('overlay')
|
||||
@shouldUpdateOverlaysState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
didAddDecoration: (decoration) ->
|
||||
@observeDecoration(decoration)
|
||||
|
||||
if decoration.isType('line') or decoration.isType('gutter')
|
||||
@shouldUpdateDecorations = true
|
||||
@shouldUpdateLinesState = true if decoration.isType('line')
|
||||
if decoration.isType('line-number')
|
||||
@shouldUpdateLineNumbersState = true
|
||||
else if decoration.isType('gutter')
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
else if decoration.isType('highlight')
|
||||
@shouldUpdateDecorations = true
|
||||
else if decoration.isType('overlay')
|
||||
@shouldUpdateOverlaysState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
fetchDecorations: ->
|
||||
@decorations = []
|
||||
|
||||
return unless 0 <= @startRow <= @endRow <= Infinity
|
||||
|
||||
for markerId, decorations of @model.decorationsForScreenRowRange(@startRow, @endRow - 1)
|
||||
range = @model.getMarker(markerId).getScreenRange()
|
||||
for decoration in decorations
|
||||
@decorations.push({decoration, range})
|
||||
@decorations = @model.decorationsStateForScreenRowRange(@startRow, @endRow - 1)
|
||||
|
||||
updateLineDecorations: ->
|
||||
@rangesByDecorationId = {}
|
||||
@lineDecorationsByScreenRow = {}
|
||||
@lineNumberDecorationsByScreenRow = {}
|
||||
@customGutterDecorationsByGutterNameAndScreenRow = {}
|
||||
@customGutterDecorationsByGutterName = {}
|
||||
|
||||
for {decoration, range} in @decorations
|
||||
if decoration.isType('line') or decoration.isType('gutter')
|
||||
@addToLineDecorationCaches(decoration, range)
|
||||
for decorationId, decorationState of @decorations
|
||||
{properties, screenRange, rangeIsReversed} = decorationState
|
||||
if Decoration.isType(properties, 'line') or Decoration.isType(properties, 'line-number')
|
||||
@addToLineDecorationCaches(decorationId, properties, screenRange, rangeIsReversed)
|
||||
|
||||
else if Decoration.isType(properties, 'gutter') and properties.gutterName?
|
||||
@customGutterDecorationsByGutterName[properties.gutterName] ?= {}
|
||||
@customGutterDecorationsByGutterName[properties.gutterName][decorationId] = decorationState
|
||||
|
||||
return
|
||||
|
||||
updateHighlightDecorations: ->
|
||||
@visibleHighlights = {}
|
||||
|
||||
for {decoration, range} in @decorations
|
||||
if decoration.isType('highlight')
|
||||
@updateHighlightState(decoration, range)
|
||||
for decorationId, {properties, screenRange} of @decorations
|
||||
if Decoration.isType(properties, 'highlight')
|
||||
@updateHighlightState(decorationId, properties, screenRange)
|
||||
|
||||
for tileId, tileState of @state.content.tiles
|
||||
for id, highlight of tileState.highlights
|
||||
@@ -1276,50 +1201,29 @@ class TextEditorPresenter
|
||||
|
||||
return
|
||||
|
||||
removeFromLineDecorationCaches: (decoration) ->
|
||||
@removePropertiesFromLineDecorationCaches(decoration.id, decoration.getProperties())
|
||||
|
||||
removePropertiesFromLineDecorationCaches: (decorationId, decorationProperties) ->
|
||||
if range = @rangesByDecorationId[decorationId]
|
||||
delete @rangesByDecorationId[decorationId]
|
||||
|
||||
gutterName = decorationProperties.gutterName
|
||||
for row in [range.start.row..range.end.row] by 1
|
||||
delete @lineDecorationsByScreenRow[row]?[decorationId]
|
||||
delete @lineNumberDecorationsByScreenRow[row]?[decorationId]
|
||||
delete @customGutterDecorationsByGutterNameAndScreenRow[gutterName]?[row]?[decorationId] if gutterName
|
||||
return
|
||||
|
||||
addToLineDecorationCaches: (decoration, range) ->
|
||||
marker = decoration.getMarker()
|
||||
properties = decoration.getProperties()
|
||||
|
||||
return unless marker.isValid()
|
||||
|
||||
if range.isEmpty()
|
||||
addToLineDecorationCaches: (decorationId, properties, screenRange, rangeIsReversed) ->
|
||||
if screenRange.isEmpty()
|
||||
return if properties.onlyNonEmpty
|
||||
else
|
||||
return if properties.onlyEmpty
|
||||
omitLastRow = range.end.column is 0
|
||||
omitLastRow = screenRange.end.column is 0
|
||||
|
||||
@rangesByDecorationId[decoration.id] = range
|
||||
if rangeIsReversed
|
||||
headPosition = screenRange.start
|
||||
else
|
||||
headPosition = screenRange.end
|
||||
|
||||
for row in [range.start.row..range.end.row] by 1
|
||||
continue if properties.onlyHead and row isnt marker.getHeadScreenPosition().row
|
||||
continue if omitLastRow and row is range.end.row
|
||||
for row in [screenRange.start.row..screenRange.end.row] by 1
|
||||
continue if properties.onlyHead and row isnt headPosition.row
|
||||
continue if omitLastRow and row is screenRange.end.row
|
||||
|
||||
if decoration.isType('line')
|
||||
if Decoration.isType(properties, 'line')
|
||||
@lineDecorationsByScreenRow[row] ?= {}
|
||||
@lineDecorationsByScreenRow[row][decoration.id] = decoration
|
||||
@lineDecorationsByScreenRow[row][decorationId] = properties
|
||||
|
||||
if decoration.isType('line-number')
|
||||
if Decoration.isType(properties, 'line-number')
|
||||
@lineNumberDecorationsByScreenRow[row] ?= {}
|
||||
@lineNumberDecorationsByScreenRow[row][decoration.id] = decoration
|
||||
else if decoration.isType('gutter')
|
||||
gutterName = decoration.getProperties().gutterName
|
||||
@customGutterDecorationsByGutterNameAndScreenRow[gutterName] ?= {}
|
||||
@customGutterDecorationsByGutterNameAndScreenRow[gutterName][row] ?= {}
|
||||
@customGutterDecorationsByGutterNameAndScreenRow[gutterName][row][decoration.id] = decoration
|
||||
@lineNumberDecorationsByScreenRow[row][decorationId] = properties
|
||||
|
||||
return
|
||||
|
||||
@@ -1339,46 +1243,34 @@ class TextEditorPresenter
|
||||
|
||||
intersectingRange
|
||||
|
||||
updateHighlightState: (decoration, range) ->
|
||||
updateHighlightState: (decorationId, properties, screenRange) ->
|
||||
return unless @startRow? and @endRow? and @lineHeight? and @hasPixelPositionRequirements()
|
||||
|
||||
properties = decoration.getProperties()
|
||||
marker = decoration.getMarker()
|
||||
return if screenRange.isEmpty()
|
||||
|
||||
if decoration.isDestroyed() or not marker.isValid() or range.isEmpty() or not range.intersectsRowRange(@startRow, @endRow - 1)
|
||||
return
|
||||
if screenRange.start.row < @startRow
|
||||
screenRange.start.row = @startRow
|
||||
screenRange.start.column = 0
|
||||
if screenRange.end.row >= @endRow
|
||||
screenRange.end.row = @endRow
|
||||
screenRange.end.column = 0
|
||||
|
||||
if range.start.row < @startRow
|
||||
range.start.row = @startRow
|
||||
range.start.column = 0
|
||||
if range.end.row >= @endRow
|
||||
range.end.row = @endRow
|
||||
range.end.column = 0
|
||||
return if screenRange.isEmpty()
|
||||
|
||||
return if range.isEmpty()
|
||||
|
||||
flash = decoration.consumeNextFlash()
|
||||
|
||||
startTile = @tileForRow(range.start.row)
|
||||
endTile = @tileForRow(range.end.row)
|
||||
startTile = @tileForRow(screenRange.start.row)
|
||||
endTile = @tileForRow(screenRange.end.row)
|
||||
|
||||
for tileStartRow in [startTile..endTile] by @tileSize
|
||||
rangeWithinTile = @intersectRangeWithTile(range, tileStartRow)
|
||||
rangeWithinTile = @intersectRangeWithTile(screenRange, tileStartRow)
|
||||
|
||||
continue if rangeWithinTile.isEmpty()
|
||||
|
||||
tileState = @state.content.tiles[tileStartRow] ?= {highlights: {}}
|
||||
highlightState = tileState.highlights[decoration.id] ?= {
|
||||
flashCount: 0
|
||||
flashDuration: null
|
||||
flashClass: null
|
||||
}
|
||||
|
||||
if flash?
|
||||
highlightState.flashCount++
|
||||
highlightState.flashClass = flash.class
|
||||
highlightState.flashDuration = flash.duration
|
||||
highlightState = tileState.highlights[decorationId] ?= {}
|
||||
|
||||
highlightState.flashCount = properties.flashCount
|
||||
highlightState.flashClass = properties.flashClass
|
||||
highlightState.flashDuration = properties.flashDuration
|
||||
highlightState.class = properties.class
|
||||
highlightState.deprecatedRegionClass = properties.deprecatedRegionClass
|
||||
highlightState.regions = @buildHighlightRegions(rangeWithinTile)
|
||||
@@ -1387,7 +1279,7 @@ class TextEditorPresenter
|
||||
@repositionRegionWithinTile(region, tileStartRow)
|
||||
|
||||
@visibleHighlights[tileStartRow] ?= {}
|
||||
@visibleHighlights[tileStartRow][decoration.id] = true
|
||||
@visibleHighlights[tileStartRow][decorationId] = true
|
||||
|
||||
true
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ class TextEditor extends Model
|
||||
throw error
|
||||
|
||||
state.displayBuffer = displayBuffer
|
||||
state.selectionsMarkerLayer = displayBuffer.getMarkerLayer(state.selectionsMarkerLayerId)
|
||||
state.config = atomEnvironment.config
|
||||
state.notificationManager = atomEnvironment.notifications
|
||||
state.packageManager = atomEnvironment.packages
|
||||
@@ -87,10 +88,11 @@ class TextEditor extends Model
|
||||
super
|
||||
|
||||
{
|
||||
@softTabs, @firstVisibleScreenRow, @firstVisibleScreenColumn, initialLine, initialColumn, tabLength,
|
||||
softWrapped, @displayBuffer, buffer, suppressCursorCreation, @mini, @placeholderText,
|
||||
lineNumberGutterVisible, largeFileMode, @config, @notificationManager, @packageManager,
|
||||
@clipboard, @viewRegistry, @grammarRegistry, @project, @assert, @applicationDelegate
|
||||
@softTabs, @firstVisibleScreenRow, @firstVisibleScreenColumn, initialLine,initialColumn, tabLength,
|
||||
softWrapped, @displayBuffer, @selectionsMarkerLayer, buffer, suppressCursorCreation,
|
||||
@mini, @placeholderText, lineNumberGutterVisible, largeFileMode, @config,
|
||||
@notificationManager, @packageManager, @clipboard, @viewRegistry, @grammarRegistry,
|
||||
@project, @assert, @applicationDelegate
|
||||
} = params
|
||||
|
||||
throw new Error("Must pass a config parameter when constructing TextEditors") unless @config?
|
||||
@@ -115,8 +117,9 @@ class TextEditor extends Model
|
||||
@config, @assert, @grammarRegistry, @packageManager
|
||||
})
|
||||
@buffer = @displayBuffer.buffer
|
||||
@selectionsMarkerLayer ?= @addMarkerLayer(maintainHistory: true)
|
||||
|
||||
for marker in @findMarkers(@getSelectionMarkerAttributes())
|
||||
for marker in @selectionsMarkerLayer.getMarkers()
|
||||
marker.setProperties(preserveFolds: true)
|
||||
@addSelection(marker)
|
||||
|
||||
@@ -146,6 +149,7 @@ class TextEditor extends Model
|
||||
firstVisibleScreenRow: @getFirstVisibleScreenRow()
|
||||
firstVisibleScreenColumn: @getFirstVisibleScreenColumn()
|
||||
displayBuffer: @displayBuffer.serialize()
|
||||
selectionsMarkerLayerId: @selectionsMarkerLayer.id
|
||||
|
||||
subscribeToBuffer: ->
|
||||
@buffer.retain()
|
||||
@@ -161,9 +165,9 @@ class TextEditor extends Model
|
||||
@preserveCursorPositionOnBufferReload()
|
||||
|
||||
subscribeToDisplayBuffer: ->
|
||||
@disposables.add @displayBuffer.onDidCreateMarker @handleMarkerCreated
|
||||
@disposables.add @displayBuffer.onDidChangeGrammar => @handleGrammarChange()
|
||||
@disposables.add @displayBuffer.onDidTokenize => @handleTokenization()
|
||||
@disposables.add @selectionsMarkerLayer.onDidCreateMarker @addSelection.bind(this)
|
||||
@disposables.add @displayBuffer.onDidChangeGrammar @handleGrammarChange.bind(this)
|
||||
@disposables.add @displayBuffer.onDidTokenize @handleTokenization.bind(this)
|
||||
@disposables.add @displayBuffer.onDidChange (e) =>
|
||||
@mergeIntersectingSelections()
|
||||
@emitter.emit 'did-change', e
|
||||
@@ -177,6 +181,7 @@ class TextEditor extends Model
|
||||
@disposables.dispose()
|
||||
@tabTypeSubscription.dispose()
|
||||
selection.destroy() for selection in @selections.slice()
|
||||
@selectionsMarkerLayer.destroy()
|
||||
@buffer.release()
|
||||
@displayBuffer.destroy()
|
||||
@languageMode.destroy()
|
||||
@@ -471,6 +476,9 @@ class TextEditor extends Model
|
||||
onDidUpdateMarkers: (callback) ->
|
||||
@displayBuffer.onDidUpdateMarkers(callback)
|
||||
|
||||
onDidUpdateDecorations: (callback) ->
|
||||
@displayBuffer.onDidUpdateDecorations(callback)
|
||||
|
||||
# Essential: Retrieves the current {TextBuffer}.
|
||||
getBuffer: -> @buffer
|
||||
|
||||
@@ -480,14 +488,13 @@ class TextEditor extends Model
|
||||
# Create an {TextEditor} with its initial state based on this object
|
||||
copy: ->
|
||||
displayBuffer = @displayBuffer.copy()
|
||||
selectionsMarkerLayer = displayBuffer.getMarkerLayer(@buffer.getMarkerLayer(@selectionsMarkerLayer.id).copy().id)
|
||||
softTabs = @getSoftTabs()
|
||||
newEditor = new TextEditor({
|
||||
@buffer, displayBuffer, @tabLength, softTabs, suppressCursorCreation: true,
|
||||
@config, @notificationManager, @packageManager, @clipboard, @viewRegistry,
|
||||
@grammarRegistry, @project, @assert, @applicationDelegate
|
||||
@buffer, displayBuffer, selectionsMarkerLayer, @tabLength, softTabs,
|
||||
suppressCursorCreation: true, @config, @notificationManager, @packageManager,
|
||||
@clipboard, @viewRegistry, @grammarRegistry, @project, @assert, @applicationDelegate
|
||||
})
|
||||
for marker in @findMarkers(editorId: @id)
|
||||
marker.copy(editorId: newEditor.id, preserveFolds: true)
|
||||
newEditor
|
||||
|
||||
# Controls visibility based on the given {Boolean}.
|
||||
@@ -502,6 +509,9 @@ class TextEditor extends Model
|
||||
|
||||
isMini: -> @mini
|
||||
|
||||
setUpdatedSynchronously: (updatedSynchronously) ->
|
||||
@displayBuffer.setUpdatedSynchronously(updatedSynchronously)
|
||||
|
||||
onDidChangeMini: (callback) ->
|
||||
@emitter.on 'did-change-mini', callback
|
||||
|
||||
@@ -869,116 +879,177 @@ class TextEditor extends Model
|
||||
@transact groupingInterval, =>
|
||||
fn(selection, index) for selection, index in @getSelectionsOrderedByBufferPosition()
|
||||
|
||||
# Move lines intersection the most recent selection up by one row in screen
|
||||
# coordinates.
|
||||
# Move lines intersecting the most recent selection or multiple selections
|
||||
# up by one row in screen coordinates.
|
||||
moveLineUp: ->
|
||||
selection = @getSelectedBufferRange()
|
||||
return if selection.start.row is 0
|
||||
lastRow = @buffer.getLastRow()
|
||||
return if selection.isEmpty() and selection.start.row is lastRow and @buffer.getLastLine() is ''
|
||||
selections = @getSelectedBufferRanges()
|
||||
selections.sort (a, b) -> a.compare(b)
|
||||
|
||||
if selections[0].start.row is 0
|
||||
return
|
||||
|
||||
if selections[selections.length - 1].start.row is @getLastBufferRow() and @buffer.getLastLine() is ''
|
||||
return
|
||||
|
||||
@transact =>
|
||||
foldedRows = []
|
||||
rows = [selection.start.row..selection.end.row]
|
||||
if selection.start.row isnt selection.end.row and selection.end.column is 0
|
||||
rows.pop() unless @isFoldedAtBufferRow(selection.end.row)
|
||||
newSelectionRanges = []
|
||||
|
||||
# Move line around the fold that is directly above the selection
|
||||
precedingScreenRow = @screenPositionForBufferPosition([selection.start.row]).translate([-1])
|
||||
precedingBufferRow = @bufferPositionForScreenPosition(precedingScreenRow).row
|
||||
if fold = @largestFoldContainingBufferRow(precedingBufferRow)
|
||||
insertDelta = fold.getBufferRange().getRowCount()
|
||||
else
|
||||
insertDelta = 1
|
||||
while selections.length > 0
|
||||
# Find selections spanning a contiguous set of lines
|
||||
selection = selections.shift()
|
||||
selectionsToMove = [selection]
|
||||
|
||||
for row in rows
|
||||
if fold = @displayBuffer.largestFoldStartingAtBufferRow(row)
|
||||
bufferRange = fold.getBufferRange()
|
||||
startRow = bufferRange.start.row
|
||||
endRow = bufferRange.end.row
|
||||
foldedRows.push(startRow - insertDelta)
|
||||
while selection.end.row is selections[0]?.start.row
|
||||
selectionsToMove.push(selections[0])
|
||||
selection.end.row = selections[0].end.row
|
||||
selections.shift()
|
||||
|
||||
# Compute the range spanned by all these selections...
|
||||
linesRangeStart = [selection.start.row, 0]
|
||||
if selection.end.row > selection.start.row and selection.end.column is 0
|
||||
# Don't move the last line of a multi-line selection if the selection ends at column 0
|
||||
linesRange = new Range(linesRangeStart, selection.end)
|
||||
else
|
||||
startRow = row
|
||||
endRow = row
|
||||
linesRange = new Range(linesRangeStart, [selection.end.row + 1, 0])
|
||||
|
||||
insertPosition = Point.fromObject([startRow - insertDelta])
|
||||
endPosition = Point.min([endRow + 1], @buffer.getEndPosition())
|
||||
lines = @buffer.getTextInRange([[startRow], endPosition])
|
||||
if endPosition.row is lastRow and endPosition.column > 0 and not @buffer.lineEndingForRow(endPosition.row)
|
||||
lines = "#{lines}\n"
|
||||
# If there's a fold containing either the starting row or the end row
|
||||
# of the selection then the whole fold needs to be moved and restored.
|
||||
# The initial fold range is stored and will be translated once the
|
||||
# insert delta is know.
|
||||
selectionFoldRanges = []
|
||||
foldAtSelectionStart =
|
||||
@displayBuffer.largestFoldContainingBufferRow(selection.start.row)
|
||||
foldAtSelectionEnd =
|
||||
@displayBuffer.largestFoldContainingBufferRow(selection.end.row)
|
||||
if fold = foldAtSelectionStart ? foldAtSelectionEnd
|
||||
selectionFoldRanges.push range = fold.getBufferRange()
|
||||
newEndRow = range.end.row + 1
|
||||
linesRange.end.row = newEndRow if newEndRow > linesRange.end.row
|
||||
fold.destroy()
|
||||
|
||||
@buffer.deleteRows(startRow, endRow)
|
||||
# If selected line range is preceded by a fold, one line above on screen
|
||||
# could be multiple lines in the buffer.
|
||||
precedingScreenRow = @screenRowForBufferRow(linesRange.start.row) - 1
|
||||
precedingBufferRow = @bufferRowForScreenRow(precedingScreenRow)
|
||||
insertDelta = linesRange.start.row - precedingBufferRow
|
||||
|
||||
# Any folds in the text that is moved will need to be re-created.
|
||||
# It includes the folds that were intersecting with the selection.
|
||||
rangesToRefold = selectionFoldRanges.concat(
|
||||
@outermostFoldsInBufferRowRange(linesRange.start.row, linesRange.end.row).map (fold) ->
|
||||
range = fold.getBufferRange()
|
||||
fold.destroy()
|
||||
range
|
||||
).map (range) -> range.translate([-insertDelta, 0])
|
||||
|
||||
# Make sure the inserted text doesn't go into an existing fold
|
||||
if fold = @displayBuffer.largestFoldStartingAtBufferRow(insertPosition.row)
|
||||
@unfoldBufferRow(insertPosition.row)
|
||||
foldedRows.push(insertPosition.row + endRow - startRow + fold.getBufferRange().getRowCount())
|
||||
if fold = @displayBuffer.largestFoldStartingAtBufferRow(precedingBufferRow)
|
||||
rangesToRefold.push(fold.getBufferRange().translate([linesRange.getRowCount() - 1, 0]))
|
||||
fold.destroy()
|
||||
|
||||
@buffer.insert(insertPosition, lines)
|
||||
# Delete lines spanned by selection and insert them on the preceding buffer row
|
||||
lines = @buffer.getTextInRange(linesRange)
|
||||
lines += @buffer.lineEndingForRow(linesRange.end.row - 1) unless lines[lines.length - 1] is '\n'
|
||||
@buffer.delete(linesRange)
|
||||
@buffer.insert([precedingBufferRow, 0], lines)
|
||||
|
||||
# Restore folds that existed before the lines were moved
|
||||
for foldedRow in foldedRows when 0 <= foldedRow <= @getLastBufferRow()
|
||||
@foldBufferRow(foldedRow)
|
||||
# Restore folds that existed before the lines were moved
|
||||
for rangeToRefold in rangesToRefold
|
||||
@displayBuffer.createFold(rangeToRefold.start.row, rangeToRefold.end.row)
|
||||
|
||||
@setSelectedBufferRange(selection.translate([-insertDelta]), preserveFolds: true, autoscroll: true)
|
||||
for selection in selectionsToMove
|
||||
newSelectionRanges.push(selection.translate([-insertDelta, 0]))
|
||||
|
||||
@setSelectedBufferRanges(newSelectionRanges, {autoscroll: false, preserveFolds: true})
|
||||
@autoIndentSelectedRows() if @shouldAutoIndent()
|
||||
@scrollToBufferPosition([newSelectionRanges[0].start.row, 0])
|
||||
|
||||
# Move lines intersecting the most recent selection down by one row in screen
|
||||
# coordinates.
|
||||
# Move lines intersecting the most recent selection or muiltiple selections
|
||||
# down by one row in screen coordinates.
|
||||
moveLineDown: ->
|
||||
selection = @getSelectedBufferRange()
|
||||
lastRow = @buffer.getLastRow()
|
||||
return if selection.end.row is lastRow
|
||||
return if selection.end.row is lastRow - 1 and @buffer.getLastLine() is ''
|
||||
selections = @getSelectedBufferRanges()
|
||||
selections.sort (a, b) -> a.compare(b)
|
||||
selections = selections.reverse()
|
||||
|
||||
@transact =>
|
||||
foldedRows = []
|
||||
rows = [selection.end.row..selection.start.row]
|
||||
if selection.start.row isnt selection.end.row and selection.end.column is 0
|
||||
rows.shift() unless @isFoldedAtBufferRow(selection.end.row)
|
||||
@consolidateSelections()
|
||||
newSelectionRanges = []
|
||||
|
||||
# Move line around the fold that is directly below the selection
|
||||
followingScreenRow = @screenPositionForBufferPosition([selection.end.row]).translate([1])
|
||||
followingBufferRow = @bufferPositionForScreenPosition(followingScreenRow).row
|
||||
if fold = @largestFoldContainingBufferRow(followingBufferRow)
|
||||
insertDelta = fold.getBufferRange().getRowCount()
|
||||
else
|
||||
insertDelta = 1
|
||||
while selections.length > 0
|
||||
# Find selections spanning a contiguous set of lines
|
||||
selection = selections.shift()
|
||||
selectionsToMove = [selection]
|
||||
|
||||
for row in rows
|
||||
if fold = @displayBuffer.largestFoldStartingAtBufferRow(row)
|
||||
bufferRange = fold.getBufferRange()
|
||||
startRow = bufferRange.start.row
|
||||
endRow = bufferRange.end.row
|
||||
foldedRows.push(endRow + insertDelta)
|
||||
# if the current selection start row matches the next selections' end row - make them one selection
|
||||
while selection.start.row is selections[0]?.end.row
|
||||
selectionsToMove.push(selections[0])
|
||||
selection.start.row = selections[0].start.row
|
||||
selections.shift()
|
||||
|
||||
# Compute the range spanned by all these selections...
|
||||
linesRangeStart = [selection.start.row, 0]
|
||||
if selection.end.row > selection.start.row and selection.end.column is 0
|
||||
# Don't move the last line of a multi-line selection if the selection ends at column 0
|
||||
linesRange = new Range(linesRangeStart, selection.end)
|
||||
else
|
||||
startRow = row
|
||||
endRow = row
|
||||
linesRange = new Range(linesRangeStart, [selection.end.row + 1, 0])
|
||||
|
||||
if endRow + 1 is lastRow
|
||||
endPosition = [endRow, @buffer.lineLengthForRow(endRow)]
|
||||
else
|
||||
endPosition = [endRow + 1]
|
||||
lines = @buffer.getTextInRange([[startRow], endPosition])
|
||||
@buffer.deleteRows(startRow, endRow)
|
||||
# If there's a fold containing either the starting row or the end row
|
||||
# of the selection then the whole fold needs to be moved and restored.
|
||||
# The initial fold range is stored and will be translated once the
|
||||
# insert delta is know.
|
||||
selectionFoldRanges = []
|
||||
foldAtSelectionStart =
|
||||
@displayBuffer.largestFoldContainingBufferRow(selection.start.row)
|
||||
foldAtSelectionEnd =
|
||||
@displayBuffer.largestFoldContainingBufferRow(selection.end.row)
|
||||
if fold = foldAtSelectionStart ? foldAtSelectionEnd
|
||||
selectionFoldRanges.push range = fold.getBufferRange()
|
||||
newEndRow = range.end.row + 1
|
||||
linesRange.end.row = newEndRow if newEndRow > linesRange.end.row
|
||||
fold.destroy()
|
||||
|
||||
insertPosition = Point.min([startRow + insertDelta], @buffer.getEndPosition())
|
||||
if insertPosition.row is @buffer.getLastRow() and insertPosition.column > 0
|
||||
# If selected line range is followed by a fold, one line below on screen
|
||||
# could be multiple lines in the buffer. But at the same time, if the
|
||||
# next buffer row is wrapped, one line in the buffer can represent many
|
||||
# screen rows.
|
||||
followingScreenRow = @displayBuffer.lastScreenRowForBufferRow(linesRange.end.row) + 1
|
||||
followingBufferRow = @bufferRowForScreenRow(followingScreenRow)
|
||||
insertDelta = followingBufferRow - linesRange.end.row
|
||||
|
||||
# Any folds in the text that is moved will need to be re-created.
|
||||
# It includes the folds that were intersecting with the selection.
|
||||
rangesToRefold = selectionFoldRanges.concat(
|
||||
@outermostFoldsInBufferRowRange(linesRange.start.row, linesRange.end.row).map (fold) ->
|
||||
range = fold.getBufferRange()
|
||||
fold.destroy()
|
||||
range
|
||||
).map (range) -> range.translate([insertDelta, 0])
|
||||
|
||||
# Make sure the inserted text doesn't go into an existing fold
|
||||
if fold = @displayBuffer.largestFoldStartingAtBufferRow(followingBufferRow)
|
||||
rangesToRefold.push(fold.getBufferRange().translate([insertDelta - 1, 0]))
|
||||
fold.destroy()
|
||||
|
||||
# Delete lines spanned by selection and insert them on the following correct buffer row
|
||||
insertPosition = new Point(selection.translate([insertDelta, 0]).start.row, 0)
|
||||
lines = @buffer.getTextInRange(linesRange)
|
||||
if linesRange.end.row is @buffer.getLastRow()
|
||||
lines = "\n#{lines}"
|
||||
|
||||
# Make sure the inserted text doesn't go into an existing fold
|
||||
if fold = @displayBuffer.largestFoldStartingAtBufferRow(insertPosition.row)
|
||||
@unfoldBufferRow(insertPosition.row)
|
||||
foldedRows.push(insertPosition.row + fold.getBufferRange().getRowCount())
|
||||
|
||||
@buffer.delete(linesRange)
|
||||
@buffer.insert(insertPosition, lines)
|
||||
|
||||
# Restore folds that existed before the lines were moved
|
||||
for foldedRow in foldedRows when 0 <= foldedRow <= @getLastBufferRow()
|
||||
@foldBufferRow(foldedRow)
|
||||
# Restore folds that existed before the lines were moved
|
||||
for rangeToRefold in rangesToRefold
|
||||
@displayBuffer.createFold(rangeToRefold.start.row, rangeToRefold.end.row)
|
||||
|
||||
@setSelectedBufferRange(selection.translate([insertDelta]), preserveFolds: true, autoscroll: true)
|
||||
for selection in selectionsToMove
|
||||
newSelectionRanges.push(selection.translate([insertDelta, 0]))
|
||||
|
||||
@setSelectedBufferRanges(newSelectionRanges, {autoscroll: false, preserveFolds: true})
|
||||
@autoIndentSelectedRows() if @shouldAutoIndent()
|
||||
@scrollToBufferPosition([newSelectionRanges[0].start.row - 1, 0])
|
||||
|
||||
# Duplicate the most recent cursor's current line.
|
||||
duplicateLines: ->
|
||||
@@ -1335,9 +1406,9 @@ class TextEditor extends Model
|
||||
Section: Decorations
|
||||
###
|
||||
|
||||
# Essential: Adds a decoration that tracks a {Marker}. When the marker moves,
|
||||
# is invalidated, or is destroyed, the decoration will be updated to reflect
|
||||
# the marker's state.
|
||||
# Essential: Add a decoration that tracks a {TextEditorMarker}. When the
|
||||
# marker moves, is invalidated, or is destroyed, the decoration will be
|
||||
# updated to reflect the marker's state.
|
||||
#
|
||||
# The following are the supported decorations types:
|
||||
#
|
||||
@@ -1356,28 +1427,28 @@ class TextEditor extends Model
|
||||
# </div>
|
||||
# ```
|
||||
# * __overlay__: Positions the view associated with the given item at the head
|
||||
# or tail of the given `Marker`.
|
||||
# * __gutter__: A decoration that tracks a {Marker} in a {Gutter}. Gutter
|
||||
# or tail of the given `TextEditorMarker`.
|
||||
# * __gutter__: A decoration that tracks a {TextEditorMarker} in a {Gutter}. Gutter
|
||||
# decorations are created by calling {Gutter::decorateMarker} on the
|
||||
# desired `Gutter` instance.
|
||||
#
|
||||
# ## Arguments
|
||||
#
|
||||
# * `marker` A {Marker} you want this decoration to follow.
|
||||
# * `marker` A {TextEditorMarker} you want this decoration to follow.
|
||||
# * `decorationParams` An {Object} representing the decoration e.g.
|
||||
# `{type: 'line-number', class: 'linter-error'}`
|
||||
# * `type` There are several supported decoration types. The behavior of the
|
||||
# types are as follows:
|
||||
# * `line` Adds the given `class` to the lines overlapping the rows
|
||||
# spanned by the `Marker`.
|
||||
# spanned by the `TextEditorMarker`.
|
||||
# * `line-number` Adds the given `class` to the line numbers overlapping
|
||||
# the rows spanned by the `Marker`.
|
||||
# the rows spanned by the `TextEditorMarker`.
|
||||
# * `highlight` Creates a `.highlight` div with the nested class with up
|
||||
# to 3 nested regions that fill the area spanned by the `Marker`.
|
||||
# to 3 nested regions that fill the area spanned by the `TextEditorMarker`.
|
||||
# * `overlay` Positions the view associated with the given item at the
|
||||
# head or tail of the given `Marker`, depending on the `position`
|
||||
# head or tail of the given `TextEditorMarker`, depending on the `position`
|
||||
# property.
|
||||
# * `gutter` Tracks a {Marker} in a {Gutter}. Created by calling
|
||||
# * `gutter` Tracks a {TextEditorMarker} in a {Gutter}. Created by calling
|
||||
# {Gutter::decorateMarker} on the desired `Gutter` instance.
|
||||
# * `class` This CSS class will be applied to the decorated line number,
|
||||
# line, highlight, or overlay.
|
||||
@@ -1385,35 +1456,53 @@ class TextEditor extends Model
|
||||
# corresponding view registered. Only applicable to the `gutter` and
|
||||
# `overlay` types.
|
||||
# * `onlyHead` (optional) If `true`, the decoration will only be applied to
|
||||
# the head of the `Marker`. Only applicable to the `line` and
|
||||
# the head of the `TextEditorMarker`. Only applicable to the `line` and
|
||||
# `line-number` types.
|
||||
# * `onlyEmpty` (optional) If `true`, the decoration will only be applied if
|
||||
# the associated `Marker` is empty. Only applicable to the `gutter`,
|
||||
# the associated `TextEditorMarker` is empty. Only applicable to the `gutter`,
|
||||
# `line`, and `line-number` types.
|
||||
# * `onlyNonEmpty` (optional) If `true`, the decoration will only be applied
|
||||
# if the associated `Marker` is non-empty. Only applicable to the
|
||||
# if the associated `TextEditorMarker` is non-empty. Only applicable to the
|
||||
# `gutter`, `line`, and `line-number` types.
|
||||
# * `position` (optional) Only applicable to decorations of type `overlay`,
|
||||
# controls where the overlay view is positioned relative to the `Marker`.
|
||||
# controls where the overlay view is positioned relative to the `TextEditorMarker`.
|
||||
# Values can be `'head'` (the default), or `'tail'`.
|
||||
#
|
||||
# Returns a {Decoration} object
|
||||
decorateMarker: (marker, decorationParams) ->
|
||||
@displayBuffer.decorateMarker(marker, decorationParams)
|
||||
|
||||
# Essential: Get all the decorations within a screen row range.
|
||||
# Essential: *Experimental:* Add a decoration to every marker in the given
|
||||
# marker layer. Can be used to decorate a large number of markers without
|
||||
# having to create and manage many individual decorations.
|
||||
#
|
||||
# * `markerLayer` A {TextEditorMarkerLayer} or {MarkerLayer} to decorate.
|
||||
# * `decorationParams` The same parameters that are passed to
|
||||
# {decorateMarker}, except the `type` cannot be `overlay` or `gutter`.
|
||||
#
|
||||
# This API is experimental and subject to change on any release.
|
||||
#
|
||||
# Returns a {LayerDecoration}.
|
||||
decorateMarkerLayer: (markerLayer, decorationParams) ->
|
||||
@displayBuffer.decorateMarkerLayer(markerLayer, decorationParams)
|
||||
|
||||
# Deprecated: Get all the decorations within a screen row range on the default
|
||||
# layer.
|
||||
#
|
||||
# * `startScreenRow` the {Number} beginning screen row
|
||||
# * `endScreenRow` the {Number} end screen row (inclusive)
|
||||
#
|
||||
# Returns an {Object} of decorations in the form
|
||||
# `{1: [{id: 10, type: 'line-number', class: 'someclass'}], 2: ...}`
|
||||
# where the keys are {Marker} IDs, and the values are an array of decoration
|
||||
# where the keys are {TextEditorMarker} IDs, and the values are an array of decoration
|
||||
# params objects attached to the marker.
|
||||
# Returns an empty object when no decorations are found
|
||||
decorationsForScreenRowRange: (startScreenRow, endScreenRow) ->
|
||||
@displayBuffer.decorationsForScreenRowRange(startScreenRow, endScreenRow)
|
||||
|
||||
decorationsStateForScreenRowRange: (startScreenRow, endScreenRow) ->
|
||||
@displayBuffer.decorationsStateForScreenRowRange(startScreenRow, endScreenRow)
|
||||
|
||||
# Extended: Get all decorations.
|
||||
#
|
||||
# * `propertyFilter` (optional) An {Object} containing key value pairs that
|
||||
@@ -1469,10 +1558,10 @@ class TextEditor extends Model
|
||||
Section: Markers
|
||||
###
|
||||
|
||||
# Essential: Create a marker with the given range in buffer coordinates. This
|
||||
# marker will maintain its logical location as the buffer is changed, so if
|
||||
# you mark a particular word, the marker will remain over that word even if
|
||||
# the word's location in the buffer changes.
|
||||
# Essential: Create a marker on the default marker layer with the given range
|
||||
# in buffer coordinates. This marker will maintain its logical location as the
|
||||
# buffer is changed, so if you mark a particular word, the marker will remain
|
||||
# over that word even if the word's location in the buffer changes.
|
||||
#
|
||||
# * `range` A {Range} or range-compatible {Array}
|
||||
# * `properties` A hash of key-value pairs to associate with the marker. There
|
||||
@@ -1500,14 +1589,14 @@ class TextEditor extends Model
|
||||
# region in any way, including changes that end at the marker's
|
||||
# start or start at the marker's end. This is the most fragile strategy.
|
||||
#
|
||||
# Returns a {Marker}.
|
||||
# Returns a {TextEditorMarker}.
|
||||
markBufferRange: (args...) ->
|
||||
@displayBuffer.markBufferRange(args...)
|
||||
|
||||
# Essential: Create a marker with the given range in screen coordinates. This
|
||||
# marker will maintain its logical location as the buffer is changed, so if
|
||||
# you mark a particular word, the marker will remain over that word even if
|
||||
# the word's location in the buffer changes.
|
||||
# Essential: Create a marker on the default marker layer with the given range
|
||||
# in screen coordinates. This marker will maintain its logical location as the
|
||||
# buffer is changed, so if you mark a particular word, the marker will remain
|
||||
# over that word even if the word's location in the buffer changes.
|
||||
#
|
||||
# * `range` A {Range} or range-compatible {Array}
|
||||
# * `properties` A hash of key-value pairs to associate with the marker. There
|
||||
@@ -1535,29 +1624,32 @@ class TextEditor extends Model
|
||||
# region in any way, including changes that end at the marker's
|
||||
# start or start at the marker's end. This is the most fragile strategy.
|
||||
#
|
||||
# Returns a {Marker}.
|
||||
# Returns a {TextEditorMarker}.
|
||||
markScreenRange: (args...) ->
|
||||
@displayBuffer.markScreenRange(args...)
|
||||
|
||||
# Essential: Mark the given position in buffer coordinates.
|
||||
# Essential: Mark the given position in buffer coordinates on the default
|
||||
# marker layer.
|
||||
#
|
||||
# * `position` A {Point} or {Array} of `[row, column]`.
|
||||
# * `options` (optional) See {TextBuffer::markRange}.
|
||||
#
|
||||
# Returns a {Marker}.
|
||||
# Returns a {TextEditorMarker}.
|
||||
markBufferPosition: (args...) ->
|
||||
@displayBuffer.markBufferPosition(args...)
|
||||
|
||||
# Essential: Mark the given position in screen coordinates.
|
||||
# Essential: Mark the given position in screen coordinates on the default
|
||||
# marker layer.
|
||||
#
|
||||
# * `position` A {Point} or {Array} of `[row, column]`.
|
||||
# * `options` (optional) See {TextBuffer::markRange}.
|
||||
#
|
||||
# Returns a {Marker}.
|
||||
# Returns a {TextEditorMarker}.
|
||||
markScreenPosition: (args...) ->
|
||||
@displayBuffer.markScreenPosition(args...)
|
||||
|
||||
# Essential: Find all {Marker}s that match the given properties.
|
||||
# Essential: Find all {TextEditorMarker}s on the default marker layer that
|
||||
# match the given properties.
|
||||
#
|
||||
# This method finds markers based on the given properties. Markers can be
|
||||
# associated with custom properties that will be compared with basic equality.
|
||||
@@ -1579,44 +1671,60 @@ class TextEditor extends Model
|
||||
findMarkers: (properties) ->
|
||||
@displayBuffer.findMarkers(properties)
|
||||
|
||||
# Extended: Observe changes in the set of markers that intersect a particular
|
||||
# region of the editor.
|
||||
#
|
||||
# * `callback` A {Function} to call whenever one or more {Marker}s appears,
|
||||
# disappears, or moves within the given region.
|
||||
# * `event` An {Object} with the following keys:
|
||||
# * `insert` A {Set} containing the ids of all markers that appeared
|
||||
# in the range.
|
||||
# * `update` A {Set} containing the ids of all markers that moved within
|
||||
# the region.
|
||||
# * `remove` A {Set} containing the ids of all markers that disappeared
|
||||
# from the region.
|
||||
#
|
||||
# Returns a {MarkerObservationWindow}, which allows you to specify the region
|
||||
# of interest by calling {MarkerObservationWindow::setBufferRange} or
|
||||
# {MarkerObservationWindow::setScreenRange}.
|
||||
observeMarkers: (callback) ->
|
||||
@displayBuffer.observeMarkers(callback)
|
||||
|
||||
# Extended: Get the {Marker} for the given marker id.
|
||||
# Extended: Get the {TextEditorMarker} on the default layer for the given
|
||||
# marker id.
|
||||
#
|
||||
# * `id` {Number} id of the marker
|
||||
getMarker: (id) ->
|
||||
@displayBuffer.getMarker(id)
|
||||
|
||||
# Extended: Get all {Marker}s. Consider using {::findMarkers}
|
||||
# Extended: Get all {TextEditorMarker}s on the default marker layer. Consider
|
||||
# using {::findMarkers}
|
||||
getMarkers: ->
|
||||
@displayBuffer.getMarkers()
|
||||
|
||||
# Extended: Get the number of markers in this editor's buffer.
|
||||
# Extended: Get the number of markers in the default marker layer.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getMarkerCount: ->
|
||||
@buffer.getMarkerCount()
|
||||
|
||||
# {Delegates to: DisplayBuffer.destroyMarker}
|
||||
destroyMarker: (args...) ->
|
||||
@displayBuffer.destroyMarker(args...)
|
||||
destroyMarker: (id) ->
|
||||
@getMarker(id)?.destroy()
|
||||
|
||||
# Extended: *Experimental:* Create a marker layer to group related markers.
|
||||
#
|
||||
# * `options` An {Object} containing the following keys:
|
||||
# * `maintainHistory` A {Boolean} indicating whether marker state should be
|
||||
# restored on undo/redo. Defaults to `false`.
|
||||
#
|
||||
# This API is experimental and subject to change on any release.
|
||||
#
|
||||
# Returns a {TextEditorMarkerLayer}.
|
||||
addMarkerLayer: (options) ->
|
||||
@displayBuffer.addMarkerLayer(options)
|
||||
|
||||
# Public: *Experimental:* Get a {TextEditorMarkerLayer} by id.
|
||||
#
|
||||
# * `id` The id of the marker layer to retrieve.
|
||||
#
|
||||
# This API is experimental and subject to change on any release.
|
||||
#
|
||||
# Returns a {MarkerLayer} or `undefined` if no layer exists with the given
|
||||
# id.
|
||||
getMarkerLayer: (id) ->
|
||||
@displayBuffer.getMarkerLayer(id)
|
||||
|
||||
# Public: *Experimental:* Get the default {TextEditorMarkerLayer}.
|
||||
#
|
||||
# All marker APIs not tied to an explicit layer interact with this default
|
||||
# layer.
|
||||
#
|
||||
# This API is experimental and subject to change on any release.
|
||||
#
|
||||
# Returns a {TextEditorMarkerLayer}.
|
||||
getDefaultMarkerLayer: ->
|
||||
@displayBuffer.getDefaultMarkerLayer()
|
||||
|
||||
###
|
||||
Section: Cursors
|
||||
@@ -1686,7 +1794,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns a {Cursor}.
|
||||
addCursorAtBufferPosition: (bufferPosition, options) ->
|
||||
@markBufferPosition(bufferPosition, @getSelectionMarkerAttributes())
|
||||
@selectionsMarkerLayer.markBufferPosition(bufferPosition, @getSelectionMarkerAttributes())
|
||||
@getLastSelection().cursor.autoscroll() unless options?.autoscroll is false
|
||||
@getLastSelection().cursor
|
||||
|
||||
@@ -1696,7 +1804,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns a {Cursor}.
|
||||
addCursorAtScreenPosition: (screenPosition, options) ->
|
||||
@markScreenPosition(screenPosition, @getSelectionMarkerAttributes())
|
||||
@selectionsMarkerLayer.markScreenPosition(screenPosition, @getSelectionMarkerAttributes())
|
||||
@getLastSelection().cursor.autoscroll() unless options?.autoscroll is false
|
||||
@getLastSelection().cursor
|
||||
|
||||
@@ -1821,7 +1929,7 @@ class TextEditor extends Model
|
||||
getCursorsOrderedByBufferPosition: ->
|
||||
@getCursors().sort (a, b) -> a.compare(b)
|
||||
|
||||
# Add a cursor based on the given {Marker}.
|
||||
# Add a cursor based on the given {TextEditorMarker}.
|
||||
addCursor: (marker) ->
|
||||
cursor = new Cursor(editor: this, marker: marker, config: @config)
|
||||
@cursors.push(cursor)
|
||||
@@ -1974,7 +2082,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns the added {Selection}.
|
||||
addSelectionForBufferRange: (bufferRange, options={}) ->
|
||||
@markBufferRange(bufferRange, _.defaults(@getSelectionMarkerAttributes(), options))
|
||||
@selectionsMarkerLayer.markBufferRange(bufferRange, _.defaults(@getSelectionMarkerAttributes(), options))
|
||||
@getLastSelection().autoscroll() unless options.autoscroll is false
|
||||
@getLastSelection()
|
||||
|
||||
@@ -1987,7 +2095,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns the added {Selection}.
|
||||
addSelectionForScreenRange: (screenRange, options={}) ->
|
||||
@markScreenRange(screenRange, _.defaults(@getSelectionMarkerAttributes(), options))
|
||||
@selectionsMarkerLayer.markScreenRange(screenRange, _.defaults(@getSelectionMarkerAttributes(), options))
|
||||
@getLastSelection().autoscroll() unless options.autoscroll is false
|
||||
@getLastSelection()
|
||||
|
||||
@@ -2170,7 +2278,7 @@ class TextEditor extends Model
|
||||
|
||||
# Extended: Select the range of the given marker if it is valid.
|
||||
#
|
||||
# * `marker` A {Marker}
|
||||
# * `marker` A {TextEditorMarker}
|
||||
#
|
||||
# Returns the selected {Range} or `undefined` if the marker is invalid.
|
||||
selectMarker: (marker) ->
|
||||
@@ -2296,9 +2404,9 @@ class TextEditor extends Model
|
||||
_.reduce(tail, reducer, [head])
|
||||
return result if fn?
|
||||
|
||||
# Add a {Selection} based on the given {Marker}.
|
||||
# Add a {Selection} based on the given {TextEditorMarker}.
|
||||
#
|
||||
# * `marker` The {Marker} to highlight
|
||||
# * `marker` The {TextEditorMarker} to highlight
|
||||
# * `options` (optional) An {Object} that pertains to the {Selection} constructor.
|
||||
#
|
||||
# Returns the new {Selection}.
|
||||
@@ -3006,10 +3114,6 @@ class TextEditor extends Model
|
||||
@subscribeToTabTypeConfig()
|
||||
@emitter.emit 'did-change-grammar', @getGrammar()
|
||||
|
||||
handleMarkerCreated: (marker) =>
|
||||
if marker.matchesProperties(@getSelectionMarkerAttributes())
|
||||
@addSelection(marker)
|
||||
|
||||
###
|
||||
Section: TextEditor Rendering
|
||||
###
|
||||
@@ -3038,7 +3142,7 @@ class TextEditor extends Model
|
||||
@viewRegistry.getView(this).pixelPositionForScreenPosition(screenPosition)
|
||||
|
||||
getSelectionMarkerAttributes: ->
|
||||
{type: 'selection', editorId: @id, invalidate: 'never', maintainHistory: true}
|
||||
{type: 'selection', invalidate: 'never'}
|
||||
|
||||
getVerticalScrollMargin: -> @displayBuffer.getVerticalScrollMargin()
|
||||
setVerticalScrollMargin: (verticalScrollMargin) -> @displayBuffer.setVerticalScrollMargin(verticalScrollMargin)
|
||||
|
||||
@@ -43,7 +43,7 @@ _ = require 'underscore-plus'
|
||||
# ```
|
||||
module.exports =
|
||||
class ViewRegistry
|
||||
documentUpdateRequested: false
|
||||
animationFrameRequest: null
|
||||
documentReadInProgress: false
|
||||
performDocumentPollAfterUpdate: false
|
||||
debouncedPerformDocumentPoll: null
|
||||
@@ -195,20 +195,30 @@ class ViewRegistry
|
||||
pollAfterNextUpdate: ->
|
||||
@performDocumentPollAfterUpdate = true
|
||||
|
||||
getNextUpdatePromise: ->
|
||||
@nextUpdatePromise ?= new Promise (resolve) =>
|
||||
@resolveNextUpdatePromise = resolve
|
||||
|
||||
clearDocumentRequests: ->
|
||||
@documentReaders = []
|
||||
@documentWriters = []
|
||||
@documentPollers = []
|
||||
@documentUpdateRequested = false
|
||||
@nextUpdatePromise = null
|
||||
@resolveNextUpdatePromise = null
|
||||
if @animationFrameRequest?
|
||||
cancelAnimationFrame(@animationFrameRequest)
|
||||
@animationFrameRequest = null
|
||||
@stopPollingDocument()
|
||||
|
||||
requestDocumentUpdate: ->
|
||||
unless @documentUpdateRequested
|
||||
@documentUpdateRequested = true
|
||||
requestAnimationFrame(@performDocumentUpdate)
|
||||
@animationFrameRequest ?= requestAnimationFrame(@performDocumentUpdate)
|
||||
|
||||
performDocumentUpdate: =>
|
||||
@documentUpdateRequested = false
|
||||
resolveNextUpdatePromise = @resolveNextUpdatePromise
|
||||
@animationFrameRequest = null
|
||||
@nextUpdatePromise = null
|
||||
@resolveNextUpdatePromise = null
|
||||
|
||||
writer() while writer = @documentWriters.shift()
|
||||
|
||||
@documentReadInProgress = true
|
||||
@@ -220,6 +230,8 @@ class ViewRegistry
|
||||
# process updates requested as a result of reads
|
||||
writer() while writer = @documentWriters.shift()
|
||||
|
||||
resolveNextUpdatePromise?()
|
||||
|
||||
startPollingDocument: ->
|
||||
window.addEventListener('resize', @requestDocumentPoll)
|
||||
@observer.observe(document, {subtree: true, childList: true, attributes: true})
|
||||
@@ -229,7 +241,7 @@ class ViewRegistry
|
||||
@observer.disconnect()
|
||||
|
||||
requestDocumentPoll: =>
|
||||
if @documentUpdateRequested
|
||||
if @animationFrameRequest?
|
||||
@performDocumentPollAfterUpdate = true
|
||||
else
|
||||
@debouncedPerformDocumentPoll()
|
||||
|
||||
@@ -468,7 +468,7 @@ class Workspace extends Model
|
||||
when 'EACCES'
|
||||
@notificationManager.addWarning("Permission denied '#{error.path}'")
|
||||
return Promise.resolve()
|
||||
when 'EPERM', 'EBUSY', 'ENXIO', 'EIO', 'ENOTCONN', 'UNKNOWN', 'ECONNRESET', 'EINVAL'
|
||||
when 'EPERM', 'EBUSY', 'ENXIO', 'EIO', 'ENOTCONN', 'UNKNOWN', 'ECONNRESET', 'EINVAL', 'EMFILE'
|
||||
@notificationManager.addWarning("Unable to open '#{error.path ? uri}'", detail: error.message)
|
||||
return Promise.resolve()
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user