Comparar commits

..

50 Commits

Autor SHA1 Mensagem Data
Max Brunsfeld 6430bbb460 Wait for chromedriver's startup message on stdout 2015-02-05 22:40:17 -08:00
Max Brunsfeld 362ff963fd ⬆️ grunt-download-atom-shell 2015-02-05 13:07:05 -08:00
Max Brunsfeld 61ca2e14dc Run integration tests on CI 2015-02-05 13:02:25 -08:00
Max Brunsfeld 56677e71e4 Enable integration tests with environment var
I think this makes more sense then running them via a separate command,
since that command would only make sense for atom-core, unlike the
current 'run-package-specs' command, which works for any atom package.

This way, they won't run by default, but you can opt in to running them
on the command line by setting an env var, or in the spec runner by
temporarily editing the code, like we do for focused tests anyway.
2015-02-05 13:02:08 -08:00
Max Brunsfeld c927101bb2 Download chromedriver along with atom-shell 2015-02-05 12:59:48 -08:00
Max Brunsfeld 28f280183e Use which(1), not bash's type function 2015-02-04 15:22:51 -08:00
Max Brunsfeld f1df254a66 Use varargs in integration-test helper fn 2015-02-04 15:20:44 -08:00
Max Brunsfeld af28083a6f Add integration test coverage for reusing windows w/ the same dir 2015-02-04 15:07:08 -08:00
Max Brunsfeld 495caa7316 🎨 Rename DEFAULT_SOCKET_PATH -> DefaultSocketPath 2015-02-04 13:12:41 -08:00
Max Brunsfeld d0757c87c8 Move selenium-webdriver dependency to build/package.json 2015-02-04 13:11:33 -08:00
Max Brunsfeld c19d99e9e2 Add integration test for starting atom w/ different arguments 2015-02-04 12:59:26 -08:00
Max Brunsfeld ab43b08739 Add hidden --socket-path flag
This will allow integration tests to control which atom application
instance they use when creating atom windows
2015-02-04 11:47:29 -08:00
Ben Ogle 01bf346d0f ⬆️ metrics@0.42.0 to fix error 2015-02-04 11:36:44 -08:00
Kevin Sawicki c3bd501b84 Merge pull request #5385 from atom/ks-portable
Respect ATOM_HOME env var for portability
2015-02-04 11:22:09 -08:00
Kevin Sawicki 556bb5b65e Merge pull request #5403 from eestrada/rpmbuild
Let Linux packages fully determine executable path
2015-02-04 11:21:37 -08:00
Kevin Sawicki de1c6d1a5d Remove unused require 2015-02-04 10:54:25 -08:00
Kevin Sawicki a985942b16 Use realpath to maintain old behavior 2015-02-04 10:52:32 -08:00
Kevin Sawicki 9ba106192d 📝 Add missing in 2015-02-04 10:49:28 -08:00
Kevin Sawicki 83568a1d85 📝 Mention ATOM_HOME 2015-02-04 10:46:58 -08:00
Kevin Sawicki 71333bbc93 Remove custom Windows behavior 2015-02-04 10:42:13 -08:00
Kevin Sawicki 4e10ea0b10 Remove unused Subscriber mixin 2015-02-04 10:35:57 -08:00
Kevin Sawicki 888d6dfab5 Inline compile cache path 2015-02-04 10:35:57 -08:00
Kevin Sawicki 622b396111 Guard against no LOCALAPPDATA env var 2015-02-04 10:35:57 -08:00
Kevin Sawicki 0f2943989f 📝 Fix comment typos 2015-02-04 10:35:57 -08:00
Kevin Sawicki 63e618cf74 Add missing rootAtomFolder var 2015-02-04 10:35:57 -08:00
Kevin Sawicki 76f37277d9 Use sibling .atom when non-standard installer location 2015-02-04 10:35:57 -08:00
Kevin Sawicki 745e9c8284 📝 Default -> Defaults 2015-02-04 10:35:57 -08:00
Kevin Sawicki 456e511031 📝 Doc ATOM_HOME 2015-02-04 10:35:57 -08:00
Kevin Sawicki 390be558e2 Use ATOM_HOME when setting configDirPath 2015-02-04 10:35:57 -08:00
Kevin Sawicki 4acd5951f5 Use ATOM_HOME for nohup.out location 2015-02-04 10:35:57 -08:00
Kevin Sawicki 8cd7d7dc0c ⬆️ apm@0.135 2015-02-04 10:35:57 -08:00
Kevin Sawicki a127240f4f Use ATOM_HOME as config dir path 2015-02-04 10:35:56 -08:00
Kevin Sawicki c0ab2c4ac8 Use ATOM_HOME when building cache path 2015-02-04 10:35:56 -08:00
Kevin Sawicki 4c534606c2 Use inherited ATOM_HOME env var 2015-02-04 10:35:56 -08:00
Kevin Sawicki f330e207b4 Set ATOM_HOME env var at startup 2015-02-04 10:35:56 -08:00
Ethan Estrada d05a3f370e Have atom.desktop use PATH to find executable
Currently for RPM build only.
2015-02-04 11:20:52 -07:00
Ethan Estrada 08edcf23c9 Let Linux packages fully determine executable path
It is no longer hard coded into the atom.desktop.in file; the
individual grunt tasks now determine how the executable is accessed.
2015-02-04 11:06:20 -07:00
Kevin Sawicki dc4640eee0 ⬆️ markdown-preview@0.133 2015-02-04 09:13:52 -08:00
Kevin Sawicki 33073ea270 Merge pull request #5399 from eestrada/rpmbuild
Reverted atom.desktop.in
2015-02-04 09:05:30 -08:00
Ethan Estrada 449acc7ced Reverted atom.desktop.in
I realize the debian package build depends on this. Although it
probably won't break anything, I would rather implement a solution
that doesn't change the behavior of the debian installer.
2015-02-04 10:01:47 -07:00
Kevin Sawicki dde253160c Merge pull request #5392 from eestrada/rpmbuild
Make atom.spec.in actually use grunt variables
2015-02-04 08:59:47 -08:00
Ethan Estrada c547039f1d Automated rpm package builds to '/usr'
This is instead of the default of '/usr/local'. Targeting '/usr'
is more in line with what most RPM distros expect.
2015-02-04 01:42:52 -07:00
Ethan Estrada afb795d8cc mkrpm honors the '--install-dir' option of grunt
Also, atom.spec now uses the description provided by grunt instead
of hardcoding its own description.
2015-02-03 22:43:18 -07:00
Ethan Estrada ef8a493b9a Remove some hardcoded paths from atom.spec.in
Also, fix a couple lines in atom.spec.in that either weren't
really doing anything or were inconsistent with the rest of the
script.
2015-02-03 22:03:49 -07:00
Ethan Estrada c738145dc7 Revert "Fixes RPM install path and icon location"
This reverts commit b92e6f5a2d.
2015-02-03 21:37:14 -07:00
Ethan Estrada 93c67b670a Merge pull request #1 from atom/sm-linux-icons
Add Linux icons
2015-02-03 21:25:21 -07:00
simurai 740e6de5f3 Add Linux icons 2015-02-03 12:47:30 +09:00
Ethan Estrada 223334181b Place atom.png icons in standard system locations
This is so that the atom.desktop file will be able to find the
"atom" icon when requested. This adds the dependency of ImageMagick
to convert atom.png to varying resolutions, although, only during
the rpm build process (not during actual install). So the result
is ultimatly no different for the end user.

Also, fixed an absolute path from the rpm build process and made
it relative. It was hardcoded in the spec file to
"/tmp/atom-build/Atom/*", so builds that were made elsewhere would
have broken when attempting to package into an rpm. Now rpm packaging
should work from a build made anywhere. Also needed to modify
script/mkrpm so that it copies the build files in such a way that
they can more easily be dealt with in the spec file in a relative
way.
2015-01-31 22:14:07 -07:00
Ethan Estrada a7e18b05d3 Depend on $PATH to find executable in atom.desktop
With the atom atom executable now located in /usr/bin instead
of /usr/local/bin, it should always be available as part of the
system PATH. Thus hardcoding the filepath is not needed. Also, this
increase the flexibility of relocating the rpm at installation time
(not just build time) since the user or sys admin need only make
sure that the atom executable is in the system PATH and the
atom.desktop file will work correctly.
2015-01-31 22:12:26 -07:00
Edgard Castro b92e6f5a2d Fixes RPM install path and icon location
This makes Atom a better desktop citizen relocating to where the usual install
directory is (like on the debian package) and also fix the icon using absolute
paths, breaking icon-themes.
2015-01-31 15:04:24 -02:00
48 arquivos alterados com 987 adições e 3307 exclusões
+1 -1
Ver Arquivo
@@ -6,6 +6,6 @@
"url": "https://github.com/atom/atom.git"
},
"dependencies": {
"atom-package-manager": "0.134.0"
"atom-package-manager": "0.135.0"
}
}
+4 -4
Ver Arquivo
@@ -75,9 +75,9 @@ elif [ $OS == 'Linux' ]; then
SCRIPT=$(readlink -f "$0")
USR_DIRECTORY=$(readlink -f $(dirname $SCRIPT)/..)
ATOM_PATH="$USR_DIRECTORY/share/atom/atom"
DOT_ATOM_DIR="$HOME/.atom"
ATOM_HOME="${ATOM_HOME:-$HOME/.atom}"
mkdir -p "$DOT_ATOM_DIR"
mkdir -p "$ATOM_HOME"
: ${TMPDIR:=/tmp}
@@ -88,9 +88,9 @@ elif [ $OS == 'Linux' ]; then
exit $?
else
(
nohup "$ATOM_PATH" --executed-from="$(pwd)" --pid=$$ "$@" > "$DOT_ATOM_DIR/nohup.out" 2>&1
nohup "$ATOM_PATH" --executed-from="$(pwd)" --pid=$$ "$@" > "$ATOM_HOME/nohup.out" 2>&1
if [ $? -ne 0 ]; then
cat "$DOT_ATOM_DIR/nohup.out"
cat "$ATOM_HOME/nohup.out"
exit $?
fi
) &
+2 -2
Ver Arquivo
@@ -222,7 +222,7 @@ module.exports = (grunt) ->
grunt.registerTask('test', ['shell:kill-atom', 'run-specs'])
grunt.registerTask('docs', ['markdown:guides', 'build-docs'])
ciTasks = ['output-disk-space', 'download-atom-shell', 'build']
ciTasks = ['output-disk-space', 'download-atom-shell', 'download-atom-shell-chromedriver', 'build']
ciTasks.push('dump-symbols') if process.platform isnt 'win32'
ciTasks.push('set-version', 'check-licenses', 'lint')
ciTasks.push('mkdeb') if process.platform is 'linux'
@@ -232,6 +232,6 @@ module.exports = (grunt) ->
ciTasks.push('publish-build')
grunt.registerTask('ci', ciTasks)
defaultTasks = ['download-atom-shell', 'build', 'set-version']
defaultTasks = ['download-atom-shell', 'download-atom-shell-chromedriver', 'build', 'set-version']
defaultTasks.push 'install' unless process.platform is 'linux'
grunt.registerTask('default', defaultTasks)
+2 -1
Ver Arquivo
@@ -19,7 +19,7 @@
"grunt-contrib-csslint": "~0.1.2",
"grunt-contrib-less": "~0.8.0",
"grunt-cson": "0.14.0",
"grunt-download-atom-shell": "~0.11.0",
"grunt-download-atom-shell": "~0.12.0",
"grunt-lesslint": "0.13.0",
"grunt-peg": "~1.1.0",
"grunt-shell": "~0.3.1",
@@ -31,6 +31,7 @@
"request": "~2.27.0",
"rimraf": "~2.2.2",
"runas": "~1.0.1",
"selenium-webdriver": "^2.44.0",
"tello": "1.0.4",
"temp": "~0.8.1",
"underscore-plus": "1.x",
+2 -1
Ver Arquivo
@@ -36,8 +36,9 @@ module.exports = (grunt) ->
maintainer = 'GitHub <atom@github.com>'
installDir = '/usr'
iconName = 'atom'
executable = path.join(installDir, 'share', 'atom', 'atom')
getInstalledSize buildDir, (error, installedSize) ->
data = {name, version, description, section, arch, maintainer, installDir, iconName, installedSize}
data = {name, version, description, section, arch, maintainer, installDir, iconName, installedSize, executable}
controlFilePath = fillTemplate(path.join('resources', 'linux', 'debian', 'control'), data)
desktopFilePath = fillTemplate(path.join('resources', 'linux', 'atom.desktop'), data)
icon = path.join('resources', 'atom.png')
+2 -1
Ver Arquivo
@@ -33,8 +33,9 @@ module.exports = (grunt) ->
installDir = grunt.config.get('atom.installDir')
shareDir = path.join(installDir, 'share', 'atom')
iconName = path.join(shareDir, 'resources', 'app', 'resources', 'atom.png')
executable = 'atom'
data = {name, version, description, installDir, iconName}
data = {name, version, description, installDir, iconName, executable}
specFilePath = fillTemplate(path.join('resources', 'linux', 'redhat', 'atom.spec'), data)
desktopFilePath = fillTemplate(path.join('resources', 'linux', 'atom.desktop'), data)
+12
Ver Arquivo
@@ -85,15 +85,27 @@ module.exports = (grunt) ->
appPath = getAppPath()
resourcePath = process.cwd()
coreSpecsPath = path.resolve('spec')
chromedriverPath = path.join(resourcePath, "atom-shell", "chromedriver")
if process.platform in ['darwin', 'linux']
options =
cmd: appPath
args: ['--test', "--resource-path=#{resourcePath}", "--spec-directory=#{coreSpecsPath}"]
opts:
env: _.extend({}, process.env,
ATOM_INTEGRATION_TESTS_ENABLED: true
PATH: [process.env.path, chromedriverPath].join(":")
)
else if process.platform is 'win32'
options =
cmd: process.env.comspec
args: ['/c', appPath, '--test', "--resource-path=#{resourcePath}", "--spec-directory=#{coreSpecsPath}", "--log-file=ci.log"]
opts:
env: _.extend({}, process.env,
ATOM_INTEGRATION_TESTS_ENABLED: true
PATH: [process.env.path, chromedriverPath].join(";")
)
spawn options, (error, results, code) ->
if process.platform is 'win32'
+8
Ver Arquivo
@@ -100,6 +100,14 @@ namespaces: `core` and `editor`.
You can open this file in an editor from the _Atom > Open Your Config_ menu.
### Custom Configuration Location
You can override the location that Atom stores configuration files and folders
in by setting the `ATOM_HOME` environment variable. The `ATOM_HOME` path will be
used instead of `~/.atom` when it is set.
This option can be useful when you want to make Atom portable across machines.
### Configuration Key Reference
- `core`
+2 -2
Ver Arquivo
@@ -103,8 +103,8 @@
"incompatible-packages": "0.21.0",
"keybinding-resolver": "0.27.0",
"link": "0.30.0",
"markdown-preview": "0.132.0",
"metrics": "0.41.0",
"markdown-preview": "0.133.0",
"metrics": "0.42.0",
"notifications": "0.26.0",
"open-on-github": "0.32.0",
"package-generator": "0.37.0",
+1 -1
Ver Arquivo
@@ -2,7 +2,7 @@
Name=Atom
Comment=<%= description %>
GenericName=Text Editor
Exec=<%= installDir %>/share/atom/atom %U
Exec=<%= executable %> %U
Icon=<%= iconName %>
Type=Application
StartupNotify=true
Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 628 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 20 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 944 B

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 1.6 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 61 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 2.4 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 4.5 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 192 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 7.0 KiB

+14 -14
Ver Arquivo
@@ -1,27 +1,27 @@
Name: <%= name %>
Version: <%= version %>
Release: 0.1%{?dist}
Summary: Atom is a hackable text editor for the 21st century
Summary: <%= description %>
License: MIT
URL: https://atom.io/
AutoReqProv: no # Avoid libchromiumcontent.so missing dependency
Prefix: /usr/local
Prefix: <%= installDir %>
%description
<%= description %>
%install
mkdir -p %{buildroot}/usr/local/share/atom
cp -r /tmp/atom-build/Atom/* %{buildroot}/usr/local/share/atom
mkdir -p %{buildroot}/usr/local/bin/
ln -sf ../share/atom/resources/app/apm/node_modules/.bin/apm %{buildroot}/usr/local/bin/apm
cp atom.sh %{buildroot}/usr/local/bin/atom
chmod 755 atom.sh
mkdir -p %{buildroot}/usr/local/share/applications/
mv atom.desktop %{buildroot}/usr/local/share/applications/
mkdir -p %{buildroot}/<%= installDir %>/share/atom/
cp -r Atom/* %{buildroot}/<%= installDir %>/share/atom/
mkdir -p %{buildroot}/<%= installDir %>/bin/
ln -sf ../share/atom/resources/app/apm/node_modules/.bin/apm %{buildroot}/<%= installDir %>/bin/apm
cp atom.sh %{buildroot}/<%= installDir %>/bin/atom
chmod 755 %{buildroot}/<%= installDir %>/bin/atom
mkdir -p %{buildroot}/<%= installDir %>/share/applications/
cp atom.desktop %{buildroot}/<%= installDir %>/share/applications/
%files
/usr/local/bin/atom
/usr/local/bin/apm
/usr/local/share/atom/
/usr/local/share/applications/atom.desktop
<%= installDir %>/bin/atom
<%= installDir %>/bin/apm
<%= installDir %>/share/atom/
<%= installDir %>/share/applications/atom.desktop
+1 -1
Ver Arquivo
@@ -11,7 +11,7 @@ ARCH=`uname -m`
rpmdev-setuptree
cp -r $BUILD_DIRECTORY/Atom/* $RPM_BUILD_ROOT/BUILD
cp -r $BUILD_DIRECTORY/Atom $RPM_BUILD_ROOT/BUILD
cp $SPEC_FILE $RPM_BUILD_ROOT/SPECS
cp ./atom.sh $RPM_BUILD_ROOT/BUILD
cp $DESKTOP_FILE $RPM_BUILD_ROOT/BUILD
+1 -1
Ver Arquivo
@@ -3,4 +3,4 @@
set -e
script/build
script/grunt mkrpm publish-build --stack
script/grunt mkrpm publish-build --stack --install-dir /usr
+36
Ver Arquivo
@@ -0,0 +1,36 @@
#!/bin/bash
# This script wraps the `Atom` binary, allowing the `chromedriver` server to
# execute it with positional arguments. `chromedriver` only allows 'switches'
# to be specified when starting a browser, not positional arguments, so this
# script accepts two special switches:
#
# * `atom-path` The path to the `Atom` binary
# * `atom-args` A space-separated list of positional arguments to pass to Atom.
#
# Any other switches will be passed through to `Atom`.
atom_path=""
atom_switches=()
atom_args=()
for arg in "$@"; do
case $arg in
--atom-path=*)
atom_path="${arg#*=}"
;;
--atom-args=*)
atom_args_string="${arg#*=}"
for atom_arg in $atom_args_string; do
atom_args+=($atom_arg)
done
;;
*)
atom_switches+=($arg)
;;
esac
done
exec $atom_path "${atom_switches[@]}" "${atom_args[@]}"
+107
Ver Arquivo
@@ -0,0 +1,107 @@
# These tests are excluded by default. To run them from the command line:
#
# ATOM_INTEGRATION_TESTS_ENABLED=true apm test
return unless process.env.ATOM_INTEGRATION_TESTS_ENABLED
os = require "os"
fs = require "fs"
path = require "path"
remote = require "remote"
temp = require("temp").track()
{spawn, spawnSync} = require "child_process"
{Builder, By} = require "../../build/node_modules/selenium-webdriver"
AtomPath = remote.process.argv[0]
AtomLauncherPath = path.join(__dirname, "helpers", "atom-launcher.sh")
SocketPath = path.join(os.tmpdir(), "atom-integration-test.sock")
ChromeDriverPort = 9515
describe "Starting Atom", ->
[chromeDriver, driver, tempDirPath] = []
beforeEach ->
tempDirPath = temp.mkdirSync("empty-dir")
waitsFor "chromedriver to start", (done) ->
chromeDriver = spawn "chromedriver", ["--verbose", "--port=#{ChromeDriverPort}"]
chromeDriver.on "error", (error) ->
throw new Error("chromedriver failed to start: #{error.message}")
chromeDriver.stdout.on "data", -> done()
afterEach ->
waitsForPromise -> driver.quit().thenFinally(-> chromeDriver.kill())
startAtom = (args...) ->
driver = new Builder()
.usingServer("http://localhost:#{ChromeDriverPort}")
.withCapabilities(
chromeOptions:
binary: AtomLauncherPath
args: [
"atom-path=#{AtomPath}"
"atom-args=#{args.join(" ")}"
"dev"
"safe"
"user-data-dir=#{temp.mkdirSync('integration-spec-')}"
"socket-path=#{SocketPath}"
]
)
.forBrowser('atom')
.build()
waitsForPromise ->
driver.wait ->
driver.getTitle().then (title) -> title.indexOf("Atom") >= 0
startAnotherAtom = (args...) ->
spawnSync(AtomPath, args.concat([
"--dev",
"--safe",
"--socket-path=#{SocketPath}"
]))
describe "when given the name of a file that doesn't exist", ->
tempFilePath = null
beforeEach ->
tempFilePath = path.join(tempDirPath, "an-existing-file")
fs.writeFileSync(tempFilePath, "This was already here.")
startAtom(path.join(tempDirPath, "new-file"))
it "opens a new window with an empty text editor", ->
waitsForPromise ->
driver.getAllWindowHandles().then (handles) ->
expect(handles.length).toBe 1
driver.executeScript(-> atom.workspace.getActivePane().getItems().length).then (length) ->
expect(length).toBe 1
driver.executeScript(-> atom.workspace.getActiveTextEditor().getText()).then (text) ->
expect(text).toBe("")
driver.findElement(By.tagName("atom-text-editor")).sendKeys("Hello world!")
driver.executeScript(-> atom.workspace.getActiveTextEditor().getText()).then (text) ->
expect(text).toBe "Hello world!"
# Opening another existing file in the same directory reuses the window,
# and opens a new tab for the file.
waitsForPromise ->
startAnotherAtom(tempFilePath)
driver.wait ->
driver.executeScript(-> atom.workspace.getActivePane().getItems().length).then (length) ->
length is 2
driver.executeScript(-> atom.workspace.getActiveTextEditor().getText()).then (text) ->
expect(text).toBe "This was already here."
# Opening a different directory creates a new window.
waitsForPromise ->
startAnotherAtom(temp.mkdirSync("another-empty-dir"))
driver.wait ->
driver.getAllWindowHandles().then (handles) ->
handles.length is 2
describe "when given the name of a directory that exists", ->
beforeEach ->
startAtom(tempDirPath)
it "opens a new window no text editors open", ->
waitsForPromise ->
driver.executeScript(-> atom.workspace.getActiveTextEditor()).then (editor) ->
expect(editor).toBeNull()
+2 -2
Ver Arquivo
@@ -305,13 +305,13 @@ window.waitsForPromise = (args...) ->
window.waitsFor timeout, (moveOn) ->
promise = fn()
if shouldReject
promise.catch(moveOn)
(promise.catch ? promise.thenCatch).call(promise, moveOn)
promise.then ->
jasmine.getEnv().currentSpec.fail("Expected promise to be rejected, but it was resolved")
moveOn()
else
promise.then(moveOn)
promise.catch (error) ->
(promise.catch ? promise.thenCatch).call promise, (error) ->
jasmine.getEnv().currentSpec.fail("Expected promise to be resolved, but it was rejected with #{jasmine.pp(error)}")
moveOn()
+19 -38
Ver Arquivo
@@ -135,7 +135,7 @@ describe "TextEditorComponent", ->
expect(newLineHeightInPixels).not.toBe initialLineHeightInPixels
expect(component.lineNodeForScreenRow(1).offsetTop).toBe 1 * newLineHeightInPixels
xit "updates the top position of lines when the font family changes", ->
it "updates the top position of lines when the font family changes", ->
# Can't find a font that changes the line height, but we think one might exist
linesComponent = component.refs.lines
spyOn(linesComponent, 'measureLineHeightAndDefaultCharWidth').andCallFake -> editor.setLineHeightInPixels(10)
@@ -301,9 +301,7 @@ describe "TextEditorComponent", ->
expect(component.lineNodeForScreenRow(10).textContent).toBe nbsp
it "interleaves invisible line-ending characters with indent guides on empty lines", ->
atom.config.set "editor.showIndentGuide", true
nextAnimationFrame()
component.setShowIndentGuide(true)
editor.setTextInBufferRange([[10, 0], [11, 0]], "\r\n", normalizeLineEndings: false)
nextAnimationFrame()
expect(component.lineNodeForScreenRow(10).innerHTML).toBe '<span class="indent-guide"><span class="invisible-character">C</span><span class="invisible-character">E</span></span>'
@@ -336,8 +334,7 @@ describe "TextEditorComponent", ->
describe "when indent guides are enabled", ->
beforeEach ->
atom.config.set "editor.showIndentGuide", true
nextAnimationFrame()
component.setShowIndentGuide(true)
it "adds an 'indent-guide' class to spans comprising the leading whitespace", ->
line1LeafNodes = getLeafNodes(component.lineNodeForScreenRow(1))
@@ -429,7 +426,7 @@ describe "TextEditorComponent", ->
describe "when indent guides are disabled", ->
beforeEach ->
expect(atom.config.get("editor.showIndentGuide")).toBe false
component.setShowIndentGuide(false)
it "does not render indent guides on lines containing only whitespace", ->
editor.getBuffer().insert([1, Infinity], '\n ')
@@ -672,7 +669,7 @@ describe "TextEditorComponent", ->
expect(lineNumberHasClass(1, 'folded')).toBe false
describe "cursor rendering", ->
it "renders the currently visible cursors", ->
it "renders the currently visible cursors, translated relative to the scroll position", ->
cursor1 = editor.getLastCursor()
cursor1.setScreenPosition([0, 5])
@@ -709,16 +706,9 @@ describe "TextEditorComponent", ->
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{8 * lineHeightInPixels}px)"
expect(cursorNodes[1].style['-webkit-transform']).toBe "translate(#{10 * charWidth}px, #{4 * lineHeightInPixels}px)"
wrapperView.on 'cursor:moved', cursorMovedListener = jasmine.createSpy('cursorMovedListener')
cursor3.setScreenPosition([4, 11], autoscroll: false)
nextAnimationFrame()
expect(cursorNodes[1].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{4 * lineHeightInPixels}px)"
expect(cursorMovedListener).toHaveBeenCalled()
cursor3.destroy()
nextAnimationFrame()
cursorNodes = componentNode.querySelectorAll('.cursor')
expect(cursorNodes.length).toBe 1
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth}px, #{8 * lineHeightInPixels}px)"
@@ -799,23 +789,18 @@ describe "TextEditorComponent", ->
cursorsNode = componentNode.querySelector('.cursors')
expect(cursorsNode.classList.contains('blink-off')).toBe false
advanceClock(component.props.cursorBlinkPeriod / 2)
nextAnimationFrame()
expect(cursorsNode.classList.contains('blink-off')).toBe true
advanceClock(component.props.cursorBlinkPeriod / 2)
nextAnimationFrame()
expect(cursorsNode.classList.contains('blink-off')).toBe false
# Stop blinking after moving the cursor
editor.moveRight()
nextAnimationFrame()
expect(cursorsNode.classList.contains('blink-off')).toBe false
advanceClock(component.props.cursorBlinkResumeDelay)
advanceClock(component.props.cursorBlinkPeriod / 2)
nextAnimationFrame()
expect(cursorsNode.classList.contains('blink-off')).toBe true
it "does not render cursors that are associated with non-empty selections", ->
@@ -946,6 +931,7 @@ describe "TextEditorComponent", ->
it "will flash the selection when flash:true is passed to editor::setSelectedBufferRange", ->
editor.setSelectedBufferRange([[1, 6], [1, 10]], flash: true)
nextAnimationFrame()
nextAnimationFrame() # flash starts on its own frame
selectionNode = componentNode.querySelector('.selection')
expect(selectionNode.classList.contains('flash')).toBe true
@@ -1119,14 +1105,14 @@ describe "TextEditorComponent", ->
nextAnimationFrame()
# Should not be rendering range containing the marker
expect(component.presenter.computeEndRow()).toBeLessThan 9
expect(component.getRenderedRowRange()[1]).toBeLessThan 9
regions = componentNode.querySelectorAll('.some-highlight .region')
# Nothing when outside the rendered row range
expect(regions.length).toBe 0
verticalScrollbarNode.scrollTop = 4.5 * lineHeightInPixels
verticalScrollbarNode.scrollTop = 3.5 * lineHeightInPixels
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
nextAnimationFrame()
@@ -1205,8 +1191,6 @@ describe "TextEditorComponent", ->
advanceClock(2)
decoration.flash('flash-class', 10)
nextAnimationFrame()
# Removed for 1 frame to force CSS transition to restart
expect(highlightNode.classList.contains('flash-class')).toBe false
@@ -1984,14 +1968,14 @@ describe "TextEditorComponent", ->
it "assigns the bottom/right of the scrollbars to the width of the opposite scrollbar if it is visible", ->
scrollbarCornerNode = componentNode.querySelector('.scrollbar-corner')
expect(verticalScrollbarNode.style.bottom).toBe '0px'
expect(horizontalScrollbarNode.style.right).toBe '0px'
expect(verticalScrollbarNode.style.bottom).toBe ''
expect(horizontalScrollbarNode.style.right).toBe ''
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
wrapperNode.style.width = '1000px'
component.measureHeightAndWidth()
nextAnimationFrame()
expect(verticalScrollbarNode.style.bottom).toBe '0px'
expect(verticalScrollbarNode.style.bottom).toBe ''
expect(horizontalScrollbarNode.style.right).toBe verticalScrollbarNode.offsetWidth + 'px'
expect(scrollbarCornerNode.style.display).toBe 'none'
@@ -2006,7 +1990,7 @@ describe "TextEditorComponent", ->
component.measureHeightAndWidth()
nextAnimationFrame()
expect(verticalScrollbarNode.style.bottom).toBe horizontalScrollbarNode.offsetHeight + 'px'
expect(horizontalScrollbarNode.style.right).toBe '0px'
expect(horizontalScrollbarNode.style.right).toBe ''
expect(scrollbarCornerNode.style.display).toBe 'none'
it "accounts for the width of the gutter in the scrollWidth of the horizontal scrollbar", ->
@@ -2091,7 +2075,7 @@ describe "TextEditorComponent", ->
componentNode.dispatchEvent(wheelEvent)
nextAnimationFrame()
expect(component.presenter.mouseWheelScreenRow).toBe null
expect(component.mouseWheelScreenRow).toBe null
it "clears the mouseWheelScreenRow after a delay even if the event does not cause scrolling", ->
expect(editor.getScrollTop()).toBe 0
@@ -2100,12 +2084,13 @@ describe "TextEditorComponent", ->
wheelEvent = new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: 10)
Object.defineProperty(wheelEvent, 'target', get: -> lineNode)
componentNode.dispatchEvent(wheelEvent)
expect(nextAnimationFrame).toBe noAnimationFrame
expect(editor.getScrollTop()).toBe 0
expect(component.presenter.mouseWheelScreenRow).toBe 0
advanceClock(component.presenter.stoppedScrollingDelay)
expect(component.presenter.mouseWheelScreenRow).toBe null
expect(component.mouseWheelScreenRow).toBe 0
advanceClock(component.mouseWheelScreenRowClearDelay)
expect(component.mouseWheelScreenRow).toBe null
it "does not preserve the line if it is on screen", ->
expect(componentNode.querySelectorAll('.line-number').length).toBe 14 # dummy line
@@ -2116,8 +2101,9 @@ describe "TextEditorComponent", ->
wheelEvent = new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: 100) # goes nowhere, we're already at scrollTop 0
Object.defineProperty(wheelEvent, 'target', get: -> lineNode)
componentNode.dispatchEvent(wheelEvent)
expect(nextAnimationFrame).toBe noAnimationFrame
expect(component.presenter.mouseWheelScreenRow).toBe 0
expect(component.mouseWheelScreenRow).toBe 0
editor.insertText("hello")
expect(componentNode.querySelectorAll('.line-number').length).toBe 14 # dummy line
expect(componentNode.querySelectorAll('.line').length).toBe 13
@@ -2538,7 +2524,6 @@ describe "TextEditorComponent", ->
it "does not assign a height on the component node", ->
wrapperNode.style.height = '200px'
component.measureHeightAndWidth()
nextAnimationFrame()
expect(componentNode.style.height).toBe ''
describe "when the wrapper view does not have an explicit height", ->
@@ -2596,7 +2581,6 @@ describe "TextEditorComponent", ->
it "works with the ::setEditorHeightInLines and ::setEditorWidthInChars helpers", ->
setEditorHeightInLines(wrapperView, 7)
nextAnimationFrame()
expect(componentNode.offsetHeight).toBe lineHeightInPixels * 7
setEditorWidthInChars(wrapperView, 10)
@@ -2730,7 +2714,6 @@ describe "TextEditorComponent", ->
beforeEach ->
atom.config.set 'editor.showIndentGuide', true, scopeSelector: '.source.js'
atom.config.set 'editor.showIndentGuide', false, scopeSelector: '.source.coffee'
nextAnimationFrame()
it "has an 'indent-guide' class when scoped editor.showIndentGuide is true, but not when scoped editor.showIndentGuide is false", ->
line1LeafNodes = getLeafNodes(component.lineNodeForScreenRow(1))
@@ -2739,7 +2722,6 @@ describe "TextEditorComponent", ->
expect(line1LeafNodes[1].classList.contains('indent-guide')).toBe false
editor.setGrammar(coffeeEditor.getGrammar())
nextAnimationFrame()
line1LeafNodes = getLeafNodes(component.lineNodeForScreenRow(1))
expect(line1LeafNodes[0].textContent).toBe ' '
@@ -2753,7 +2735,6 @@ describe "TextEditorComponent", ->
expect(line1LeafNodes[1].classList.contains('indent-guide')).toBe false
atom.config.set 'editor.showIndentGuide', false, scopeSelector: '.source.js'
nextAnimationFrame()
line1LeafNodes = getLeafNodes(component.lineNodeForScreenRow(1))
expect(line1LeafNodes[0].textContent).toBe ' '
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+1 -1
Ver Arquivo
@@ -96,7 +96,7 @@ getCachePath = (sourceCode) ->
unless jsCacheDir?
to5Version = require('6to5-core/package.json').version
cacheDir = path.join(fs.absolute('~/.atom'), 'compile-cache')
cacheDir = path.join(process.env.ATOM_HOME, 'compile-cache')
jsCacheDir = path.join(cacheDir, 'js', '6to5', create6to5VersionAndOptionsDigest(to5Version, defaultOptions))
path.join(jsCacheDir, "#{digest}.js")
+1 -4
Ver Arquivo
@@ -109,7 +109,7 @@ class Atom extends Model
#
# Returns the absolute path to ~/.atom
@getConfigDirPath: ->
@configDirPath ?= fs.absolute('~/.atom')
@configDirPath ?= process.env.ATOM_HOME
# Get the path to Atom's storage directory.
#
@@ -263,9 +263,6 @@ class Atom extends Model
# Make react.js faster
process.env.NODE_ENV ?= 'production' unless devMode
# Set Atom's home so packages don't have to guess it
process.env.ATOM_HOME = configDirPath
@config = new Config({configDirPath, resourcePath})
@keymaps = new KeymapManager({configDirPath, resourcePath})
@keymap = @keymaps # Deprecated
+11 -8
Ver Arquivo
@@ -14,7 +14,7 @@ url = require 'url'
{EventEmitter} = require 'events'
_ = require 'underscore-plus'
socketPath =
DefaultSocketPath =
if process.platform is 'win32'
'\\\\.\\pipe\\atom-sock'
else
@@ -31,17 +31,20 @@ class AtomApplication
# Public: The entry point into the Atom application.
@open: (options) ->
options.socketPath ?= DefaultSocketPath
createAtomApplication = -> new AtomApplication(options)
# FIXME: Sometimes when socketPath doesn't exist, net.connect would strangely
# take a few seconds to trigger 'error' event, it could be a bug of node
# or atom-shell, before it's fixed we check the existence of socketPath to
# speedup startup.
if (process.platform isnt 'win32' and not fs.existsSync socketPath) or options.test
if (process.platform isnt 'win32' and not fs.existsSync options.socketPath) or options.test
createAtomApplication()
return
client = net.connect {path: socketPath}, ->
client = net.connect {path: options.socketPath}, ->
client.write JSON.stringify(options), ->
client.end()
app.terminate()
@@ -57,7 +60,7 @@ class AtomApplication
exit: (status) -> app.exit(status)
constructor: (options) ->
{@resourcePath, @version, @devMode, @safeMode} = options
{@resourcePath, @version, @devMode, @safeMode, @socketPath} = options
# Normalize to make sure drive letter case is consistent on Windows
@resourcePath = path.normalize(@resourcePath) if @resourcePath
@@ -119,15 +122,15 @@ class AtomApplication
connection.on 'data', (data) =>
@openWithOptions(JSON.parse(data))
server.listen socketPath
server.listen @socketPath
server.on 'error', (error) -> console.error 'Application server failed', error
deleteSocketFile: ->
return if process.platform is 'win32'
if fs.existsSync(socketPath)
if fs.existsSync(@socketPath)
try
fs.unlinkSync(socketPath)
fs.unlinkSync(@socketPath)
catch error
# Ignore ENOENT errors in case the file was deleted between the exists
# check and the call to unlink sync. This occurred occasionally on CI
@@ -416,7 +419,7 @@ class AtomApplication
PackageManager = require '../package-manager'
fs = require 'fs-plus'
@packages = new PackageManager
configDirPath: fs.absolute('~/.atom')
configDirPath: process.env.ATOM_HOME
devMode: devMode
resourcePath: @resourcePath
+22 -3
Ver Arquivo
@@ -14,6 +14,7 @@ process.on 'uncaughtException', (error={}) ->
nslog(error.stack) if error.stack?
start = ->
setupAtomHome()
if process.platform is 'win32'
SquirrelUpdate = require './squirrel-update'
squirrelCommand = process.argv[1]
@@ -73,6 +74,18 @@ setupCoffeeScript = ->
js = CoffeeScript.compile(coffee, filename: filePath)
module._compile(js, filePath)
setupAtomHome = ->
return if process.env.ATOM_HOME
if process.platform is 'win32'
home = process.env.USERPROFILE
else
home = process.env.HOME
atomHome = path.join(home, '.atom')
try
atomHome = fs.realpathSync(atomHome)
process.env.ATOM_HOME = atomHome
parseCommandLine = ->
version = app.getVersion()
options = optimist(process.argv[1..])
@@ -89,8 +102,12 @@ parseCommandLine = ->
opened or a new window if it hasn't.
Environment Variables:
ATOM_DEV_RESOURCE_PATH The path from which Atom loads source code in dev mode.
Defaults to `~/github/atom`.
ATOM_DEV_RESOURCE_PATH The path from which Atom loads source code in dev mode.
Defaults to `~/github/atom`.
ATOM_HOME The root path for all configuration files and folders.
Defaults to `~/.atom`.
"""
options.alias('d', 'dev').boolean('d').describe('d', 'Run in development mode.')
options.alias('f', 'foreground').boolean('f').describe('f', 'Keep the browser process in the foreground.')
@@ -103,6 +120,7 @@ parseCommandLine = ->
options.alias('t', 'test').boolean('t').describe('t', 'Run the specified specs and exit with error code on failures.')
options.alias('v', 'version').boolean('v').describe('v', 'Print the version.')
options.alias('w', 'wait').boolean('w').describe('w', 'Wait for window to be closed before returning.')
options.string('socket-path')
args = options.argv
if args.help
@@ -123,6 +141,7 @@ parseCommandLine = ->
newWindow = args['new-window']
pidToKillWhenClosed = args['pid'] if args['wait']
logFile = args['log-file']
socketPath = args['socket-path']
if args['resource-path']
devMode = true
@@ -147,6 +166,6 @@ parseCommandLine = ->
# explicitly pass it by command line, see http://git.io/YC8_Ew.
process.env.PATH = args['path-environment'] if args['path-environment']
{resourcePath, pathsToOpen, executedFrom, test, version, pidToKillWhenClosed, devMode, safeMode, newWindow, specDirectory, logFile}
{resourcePath, pathsToOpen, executedFrom, test, version, pidToKillWhenClosed, devMode, safeMode, newWindow, specDirectory, logFile, socketPath}
start()
+1 -1
Ver Arquivo
@@ -5,7 +5,7 @@ CoffeeScript = require 'coffee-script'
CSON = require 'season'
fs = require 'fs-plus'
cacheDir = path.join(fs.absolute('~/.atom'), 'compile-cache')
cacheDir = path.join(process.env.ATOM_HOME, 'compile-cache')
stats =
hits: 0
+5 -1
Ver Arquivo
@@ -7,8 +7,12 @@ CursorComponent = React.createClass
displayName: 'CursorComponent'
render: ->
{pixelRect} = @props
{pixelRect, defaultCharWidth} = @props
{top, left, height, width} = pixelRect
width = defaultCharWidth if width is 0
WebkitTransform = "translate(#{left}px, #{top}px)"
div className: 'cursor', style: {height, width, WebkitTransform}
shouldComponentUpdate: (newProps) ->
not isEqualForProperties(newProps, @props, 'pixelRect', 'defaultCharWidth')
+46 -5
Ver Arquivo
@@ -7,14 +7,55 @@ CursorComponent = require './cursor-component'
module.exports =
CursorsComponent = React.createClass
displayName: 'CursorsComponent'
mixins: [SubscriberMixin]
cursorBlinkIntervalHandle: null
render: ->
{presenter} = @props
{performedInitialMeasurement, cursorPixelRects, defaultCharWidth} = @props
{blinkOff} = @state
className = 'cursors'
className += ' blink-off' if presenter.state.content.blinkCursorsOff
className += ' blink-off' if blinkOff
div {className},
if presenter.hasRequiredMeasurements()
for key, pixelRect of presenter.state.content.cursors
CursorComponent({key, pixelRect})
if performedInitialMeasurement
for key, pixelRect of cursorPixelRects
CursorComponent({key, pixelRect, defaultCharWidth})
getInitialState: ->
blinkOff: false
componentDidMount: ->
@startBlinkingCursors()
componentWillUnmount: ->
@stopBlinkingCursors()
shouldComponentUpdate: (newProps, newState) ->
not newState.blinkOff is @state.blinkOff or
not isEqualForProperties(newProps, @props, 'cursorPixelRects', 'scrollTop', 'scrollLeft', 'defaultCharWidth', 'useHardwareAcceleration')
componentWillUpdate: (newProps) ->
cursorsMoved = @props.cursorPixelRects? and
isEqualForProperties(newProps, @props, 'defaultCharWidth', 'scopedCharacterWidthsChangeCount') and
not isEqual(newProps.cursorPixelRects, @props.cursorPixelRects)
@pauseCursorBlinking() if cursorsMoved
startBlinkingCursors: ->
@toggleCursorBlinkHandle = setInterval(@toggleCursorBlink, @props.cursorBlinkPeriod / 2) if @isMounted()
startBlinkingCursorsAfterDelay: null # Created lazily
stopBlinkingCursors: ->
clearInterval(@toggleCursorBlinkHandle)
toggleCursorBlink: ->
@setState(blinkOff: not @state.blinkOff)
pauseCursorBlinking: ->
@state.blinkOff = false
@stopBlinkingCursors()
@startBlinkingCursorsAfterDelay ?= debounce(@startBlinkingCursors, @props.cursorBlinkResumeDelay)
@startBlinkingCursorsAfterDelay()
-2
Ver Arquivo
@@ -72,8 +72,6 @@ class Decoration
@emitter.emit 'did-destroy'
@emitter.dispose()
isDestroyed: -> @destroyed
###
Section: Event Subscription
###
+4 -4
Ver Arquivo
@@ -617,10 +617,10 @@ class DisplayBuffer extends Model
# bufferRange - The {Range} to convert
#
# Returns a {Range}.
screenRangeForBufferRange: (bufferRange, options) ->
screenRangeForBufferRange: (bufferRange) ->
bufferRange = Range.fromObject(bufferRange)
start = @screenPositionForBufferPosition(bufferRange.start, options)
end = @screenPositionForBufferPosition(bufferRange.end, options)
start = @screenPositionForBufferPosition(bufferRange.start)
end = @screenPositionForBufferPosition(bufferRange.end)
new Range(start, end)
# Given a screen range, this converts it into a buffer position.
@@ -897,7 +897,7 @@ class DisplayBuffer extends Model
getLineDecorations: (propertyFilter) ->
@getDecorations(propertyFilter).filter (decoration) -> decoration.isType('line')
getLineNumberDecorations: (propertyFilter) ->
getGutterDecorations: (propertyFilter) ->
@getDecorations(propertyFilter).filter (decoration) -> decoration.isType('line-number')
getHighlightDecorations: (propertyFilter) ->
+131 -63
Ver Arquivo
@@ -1,8 +1,7 @@
_ = require 'underscore-plus'
React = require 'react-atom-fork'
{div} = require 'reactionary-atom-fork'
_ = require 'underscore-plus'
{isEqual, isEqualForProperties, multiplyString, toArray} = _
{isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus'
Decoration = require './decoration'
SubscriberMixin = require './subscriber-mixin'
@@ -13,26 +12,23 @@ GutterComponent = React.createClass
displayName: 'GutterComponent'
mixins: [SubscriberMixin]
maxLineNumberDigits: null
dummyLineNumberNode: null
measuredWidth: null
render: ->
{presenter} = @props
@newState = presenter.state.gutter
@oldState ?= {lineNumbers: {}}
{scrollHeight, scrollViewHeight, backgroundColor, gutterBackgroundColor} = @props
{scrollHeight, backgroundColor} = @newState
if gutterBackgroundColor isnt 'rbga(0, 0, 0, 0)'
backgroundColor = gutterBackgroundColor
div className: 'gutter',
div className: 'line-numbers', ref: 'lineNumbers', style:
height: scrollHeight
WebkitTransform: @getTransform() if presenter.hasRequiredMeasurements()
height: Math.max(scrollHeight, scrollViewHeight)
WebkitTransform: @getTransform()
backgroundColor: backgroundColor
getTransform: ->
{useHardwareAcceleration} = @props
{scrollTop} = @newState
{scrollTop, useHardwareAcceleration} = @props
if useHardwareAcceleration
"translate3d(0px, #{-scrollTop}px, 0px)"
@@ -41,81 +37,141 @@ GutterComponent = React.createClass
componentWillMount: ->
@lineNumberNodesById = {}
@lineNumberIdsByScreenRow = {}
@screenRowsByLineNumberId = {}
@renderedDecorationsByLineNumberId = {}
componentDidMount: ->
{@maxLineNumberDigits} = @newState
@appendDummyLineNumber()
@updateLineNumbers()
@updateLineNumbers() if @props.performedInitialMeasurement
node = @getDOMNode()
node.addEventListener 'click', @onClick
node.addEventListener 'mousedown', @onMouseDown
componentDidUpdate: (oldProps) ->
{maxLineNumberDigits} = @newState
unless maxLineNumberDigits is @maxLineNumberDigits
@maxLineNumberDigits = maxLineNumberDigits
@updateDummyLineNumber()
node.remove() for id, node of @lineNumberNodesById
@oldState = {lineNumbers: {}}
@lineNumberNodesById = {}
# Only update the gutter if the visible row range has changed or if a
# non-zero-delta change to the screen lines has occurred within the current
# visible row range.
shouldComponentUpdate: (newProps) ->
return true unless isEqualForProperties(newProps, @props,
'renderedRowRange', 'scrollTop', 'lineHeightInPixels', 'mouseWheelScreenRow', 'lineDecorations',
'scrollViewHeight', 'useHardwareAcceleration', 'backgroundColor', 'gutterBackgroundColor'
)
{renderedRowRange, pendingChanges, lineDecorations} = newProps
return false unless renderedRowRange?
for change in pendingChanges when Math.abs(change.screenDelta) > 0 or Math.abs(change.bufferDelta) > 0
return true unless change.end <= renderedRowRange.start or renderedRowRange.end <= change.start
false
componentDidUpdate: (oldProps) ->
return unless @props.performedInitialMeasurement
unless isEqualForProperties(oldProps, @props, 'maxLineNumberDigits')
@updateDummyLineNumber()
@removeLineNumberNodes()
@clearScreenRowCaches() unless oldProps.lineHeightInPixels is @props.lineHeightInPixels
@updateLineNumbers()
clearScreenRowCaches: ->
@lineNumberIdsByScreenRow = {}
@screenRowsByLineNumberId = {}
# This dummy line number element holds the gutter to the appropriate width,
# since the real line numbers are absolutely positioned for performance reasons.
appendDummyLineNumber: ->
WrapperDiv.innerHTML = @buildLineNumberHTML({bufferRow: -1})
{maxLineNumberDigits} = @props
WrapperDiv.innerHTML = @buildLineNumberHTML(-1, false, maxLineNumberDigits)
@dummyLineNumberNode = WrapperDiv.children[0]
@refs.lineNumbers.getDOMNode().appendChild(@dummyLineNumberNode)
updateDummyLineNumber: ->
@dummyLineNumberNode.innerHTML = @buildLineNumberInnerHTML(0, false)
@dummyLineNumberNode.innerHTML = @buildLineNumberInnerHTML(0, false, @props.maxLineNumberDigits)
updateLineNumbers: ->
lineNumberIdsToPreserve = @appendOrUpdateVisibleLineNumberNodes()
@removeLineNumberNodes(lineNumberIdsToPreserve)
appendOrUpdateVisibleLineNumberNodes: ->
{editor, renderedRowRange, scrollTop, maxLineNumberDigits, lineDecorations} = @props
[startRow, endRow] = renderedRowRange
newLineNumberIds = null
newLineNumbersHTML = null
visibleLineNumberIds = new Set
for id, lineNumberState of @newState.lineNumbers
if @oldState.lineNumbers.hasOwnProperty(id)
@updateLineNumberNode(id, lineNumberState)
wrapCount = 0
for bufferRow, index in editor.bufferRowsForScreenRows(startRow, endRow - 1)
screenRow = startRow + index
if bufferRow is lastBufferRow
id = "#{bufferRow}-#{wrapCount++}"
else
id = bufferRow.toString()
lastBufferRow = bufferRow
wrapCount = 0
visibleLineNumberIds.add(id)
if @hasLineNumberNode(id)
@updateLineNumberNode(id, bufferRow, screenRow, wrapCount > 0)
else
newLineNumberIds ?= []
newLineNumbersHTML ?= ""
newLineNumberIds.push(id)
newLineNumbersHTML += @buildLineNumberHTML(lineNumberState)
@oldState.lineNumbers[id] = _.clone(lineNumberState)
newLineNumbersHTML += @buildLineNumberHTML(bufferRow, wrapCount > 0, maxLineNumberDigits, screenRow)
@screenRowsByLineNumberId[id] = screenRow
@lineNumberIdsByScreenRow[screenRow] = id
@renderedDecorationsByLineNumberId[id] = lineDecorations[screenRow]
if newLineNumberIds?
WrapperDiv.innerHTML = newLineNumbersHTML
newLineNumberNodes = toArray(WrapperDiv.children)
node = @refs.lineNumbers.getDOMNode()
for id, i in newLineNumberIds
for lineNumberId, i in newLineNumberIds
lineNumberNode = newLineNumberNodes[i]
@lineNumberNodesById[id] = lineNumberNode
@lineNumberNodesById[lineNumberId] = lineNumberNode
node.appendChild(lineNumberNode)
for id, lineNumberState of @oldState.lineNumbers
unless @newState.lineNumbers.hasOwnProperty(id)
@lineNumberNodesById[id].remove()
delete @lineNumberNodesById[id]
delete @oldState.lineNumbers[id]
visibleLineNumberIds
buildLineNumberHTML: (lineNumberState) ->
{screenRow, bufferRow, softWrapped, top, decorationClasses} = lineNumberState
removeLineNumberNodes: (lineNumberIdsToPreserve) ->
{mouseWheelScreenRow} = @props
node = @refs.lineNumbers.getDOMNode()
for lineNumberId, lineNumberNode of @lineNumberNodesById when not lineNumberIdsToPreserve?.has(lineNumberId)
screenRow = @screenRowsByLineNumberId[lineNumberId]
if not screenRow? or screenRow isnt mouseWheelScreenRow
delete @lineNumberNodesById[lineNumberId]
delete @lineNumberIdsByScreenRow[screenRow] if @lineNumberIdsByScreenRow[screenRow] is lineNumberId
delete @screenRowsByLineNumberId[lineNumberId]
delete @renderedDecorationsByLineNumberId[lineNumberId]
node.removeChild(lineNumberNode)
buildLineNumberHTML: (bufferRow, softWrapped, maxLineNumberDigits, screenRow) ->
{editor, lineHeightInPixels, lineDecorations} = @props
if screenRow?
style = "position: absolute; top: #{top}px;"
style = "position: absolute; top: #{screenRow * lineHeightInPixels}px;"
else
style = "visibility: hidden;"
className = @buildLineNumberClassName(lineNumberState)
innerHTML = @buildLineNumberInnerHTML(bufferRow, softWrapped)
innerHTML = @buildLineNumberInnerHTML(bufferRow, softWrapped, maxLineNumberDigits)
"<div class=\"#{className}\" style=\"#{style}\" data-buffer-row=\"#{bufferRow}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
classes = ''
if lineDecorations? and decorations = lineDecorations[screenRow]
for id, decoration of decorations
if Decoration.isType(decoration, 'line-number')
classes += decoration.class + ' '
buildLineNumberInnerHTML: (bufferRow, softWrapped) ->
{maxLineNumberDigits} = @newState
classes += "foldable " if bufferRow >= 0 and editor.isFoldableAtBufferRow(bufferRow)
classes += "line-number line-number-#{bufferRow}"
"<div class=\"#{classes}\" style=\"#{style}\" data-buffer-row=\"#{bufferRow}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
buildLineNumberInnerHTML: (bufferRow, softWrapped, maxLineNumberDigits) ->
if softWrapped
lineNumber = ""
else
@@ -125,34 +181,46 @@ GutterComponent = React.createClass
iconHTML = '<div class="icon-right"></div>'
padding + lineNumber + iconHTML
updateLineNumberNode: (lineNumberId, newLineNumberState) ->
oldLineNumberState = @oldState.lineNumbers[lineNumberId]
updateLineNumberNode: (lineNumberId, bufferRow, screenRow, softWrapped) ->
{editor, lineDecorations} = @props
node = @lineNumberNodesById[lineNumberId]
unless oldLineNumberState.foldable is newLineNumberState.foldable and _.isEqual(oldLineNumberState.decorationClasses, newLineNumberState.decorationClasses)
node.className = @buildLineNumberClassName(newLineNumberState)
oldLineNumberState.foldable = newLineNumberState.foldable
oldLineNumberState.decorationClasses = _.clone(newLineNumberState.decorationClasses)
if editor.isFoldableAtBufferRow(bufferRow)
node.classList.add('foldable')
else
node.classList.remove('foldable')
unless oldLineNumberState.top is newLineNumberState.top
node.style.top = newLineNumberState.top + 'px'
node.dataset.screenRow = newLineNumberState.screenRow
oldLineNumberState.top = newLineNumberState.top
oldLineNumberState.screenRow = newLineNumberState.screenRow
decorations = lineDecorations[screenRow]
previousDecorations = @renderedDecorationsByLineNumberId[lineNumberId]
buildLineNumberClassName: ({bufferRow, foldable, decorationClasses}) ->
className = "line-number line-number-#{bufferRow}"
className += " " + decorationClasses.join(' ') if decorationClasses?
className += " foldable" if foldable
className
if previousDecorations?
for id, decoration of previousDecorations
if Decoration.isType(decoration, 'line-number') and not @hasDecoration(decorations, decoration)
node.classList.remove(decoration.class)
if decorations?
for id, decoration of decorations
if Decoration.isType(decoration, 'line-number') and not @hasDecoration(previousDecorations, decoration)
node.classList.add(decoration.class)
unless @screenRowsByLineNumberId[lineNumberId] is screenRow
{lineHeightInPixels} = @props
node.style.top = screenRow * lineHeightInPixels + 'px'
node.dataset.screenRow = screenRow
@screenRowsByLineNumberId[lineNumberId] = screenRow
@lineNumberIdsByScreenRow[screenRow] = lineNumberId
hasDecoration: (decorations, decoration) ->
decorations? and decorations[decoration.id] is decoration
hasLineNumberNode: (lineNumberId) ->
@lineNumberNodesById.hasOwnProperty(lineNumberId)
lineNumberNodeForScreenRow: (screenRow) ->
for id, lineNumberState of @oldState.lineNumbers
if lineNumberState.screenRow is screenRow
return @lineNumberNodesById[id]
null
@lineNumberNodesById[@lineNumberIdsByScreenRow[screenRow]]
onMouseDown: (event) ->
{editor} = @props
{target} = event
lineNumber = target.parentNode
+77 -29
Ver Arquivo
@@ -5,46 +5,94 @@ React = require 'react-atom-fork'
module.exports =
HighlightComponent = React.createClass
displayName: 'HighlightComponent'
currentFlashCount: 0
currentFlashClass: null
render: ->
{state} = @props
{startPixelPosition, endPixelPosition, decoration} = @props
className = 'highlight'
className += " #{state.class}" if state.class?
className += " #{decoration.class}" if decoration.class?
div {className},
for region, i in state.regions
regionClassName = 'region'
regionClassName += " #{state.deprecatedRegionClass}" if state.deprecatedRegionClass?
div className: regionClassName, key: i, style: region
if endPixelPosition.top is startPixelPosition.top
@renderSingleLineRegions(decoration.deprecatedRegionClass)
else
@renderMultiLineRegions(decoration.deprecatedRegionClass)
componentDidMount: ->
@flashIfRequested()
{editor, decoration} = @props
if decoration.id?
@decoration = editor.decorationForId(decoration.id)
@decorationDisposable = @decoration.onDidFlash @startFlashAnimation
@startFlashAnimation()
componentDidUpdate: ->
@flashIfRequested()
componentWillUnmount: ->
@decorationDisposable?.dispose()
@decorationDisposable = null
flashIfRequested: ->
if @props.state.flashCount > @currentFlashCount
@currentFlashCount = @props.state.flashCount
startFlashAnimation: ->
return unless flash = @decoration.consumeNextFlash()
node = @getDOMNode()
{flashClass, flashDuration} = @props.state
node = @getDOMNode()
node.classList.remove(flash.class)
addFlashClass = =>
node.classList.add(flashClass)
@currentFlashClass = flashClass
@flashTimeoutId = setTimeout(removeFlashClass, flashDuration)
requestAnimationFrame =>
node.classList.add(flash.class)
clearTimeout(@flashTimeoutId)
removeFlashClass = -> node.classList.remove(flash.class)
@flashTimeoutId = setTimeout(removeFlashClass, flash.duration)
removeFlashClass = =>
node.classList.remove(@currentFlashClass)
@currentFlashClass = null
clearTimeout(@flashTimeoutId)
renderSingleLineRegions: (regionClass) ->
{startPixelPosition, endPixelPosition, lineHeightInPixels} = @props
if @currentFlashClass?
removeFlashClass()
requestAnimationFrame(addFlashClass)
else
addFlashClass()
className = 'region'
className += " #{regionClass}" if regionClass?
[
div className: className, key: 0, style:
top: startPixelPosition.top
height: lineHeightInPixels
left: startPixelPosition.left
width: endPixelPosition.left - startPixelPosition.left
]
renderMultiLineRegions: (regionClass) ->
{startPixelPosition, endPixelPosition, lineHeightInPixels} = @props
className = 'region'
className += " #{regionClass}" if regionClass?
regions = []
index = 0
# First row, extending from selection start to the right side of screen
regions.push(
div className: className, key: index++, style:
top: startPixelPosition.top
left: startPixelPosition.left
height: lineHeightInPixels
right: 0
)
# Middle rows, extending from left side to right side of screen
if endPixelPosition.top - startPixelPosition.top > lineHeightInPixels
regions.push(
div className: className, key: index++, style:
top: startPixelPosition.top + lineHeightInPixels
height: endPixelPosition.top - startPixelPosition.top - lineHeightInPixels
left: 0
right: 0
)
# Last row, extending from left side of screen to selection end
regions.push(
div className: className, key: index, style:
top: endPixelPosition.top
height: lineHeightInPixels
left: 0
width: endPixelPosition.left
)
regions
shouldComponentUpdate: (newProps) ->
not isEqualForProperties(newProps, @props, 'startPixelPosition', 'endPixelPosition', 'lineHeightInPixels', 'decoration')
+10 -4
Ver Arquivo
@@ -9,13 +9,16 @@ HighlightsComponent = React.createClass
render: ->
div className: 'highlights',
@renderHighlights()
@renderHighlights() if @props.performedInitialMeasurement
renderHighlights: ->
{presenter} = @props
{editor, highlightDecorations, lineHeightInPixels} = @props
highlightComponents = []
for key, state of presenter.state.content.highlights
highlightComponents.push(HighlightComponent({key, state}))
for markerId, {startPixelPosition, endPixelPosition, decorations} of highlightDecorations
for decoration in decorations
highlightComponents.push(HighlightComponent({editor, key: "#{markerId}-#{decoration.id}", startPixelPosition, endPixelPosition, decoration, lineHeightInPixels}))
highlightComponents
componentDidMount: ->
@@ -23,3 +26,6 @@ HighlightsComponent = React.createClass
insertionPoint = document.createElement('content')
insertionPoint.setAttribute('select', '.underlayer')
@getDOMNode().appendChild(insertionPoint)
shouldComponentUpdate: (newProps) ->
not isEqualForProperties(newProps, @props, 'highlightDecorations', 'lineHeightInPixels', 'defaultCharWidth', 'scopedCharacterWidthsChangeCount')
+1 -7
Ver Arquivo
@@ -1,14 +1,10 @@
path = require 'path'
fs = require 'fs-plus'
LessCache = require 'less-cache'
{Subscriber} = require 'emissary'
# {LessCache} wrapper used by {ThemeManager} to read stylesheets.
module.exports =
class LessCompileCache
Subscriber.includeInto(this)
@cacheDir: path.join(require('./coffee-cache').cacheDir, 'less')
@cacheDir: path.join(process.env.ATOM_HOME, 'compile-cache', 'less')
constructor: ({resourcePath, importPaths}) ->
@lessSearchPaths = [
@@ -35,5 +31,3 @@ class LessCompileCache
cssForFile: (stylesheetPath, lessContent) ->
@cache.cssForFile(stylesheetPath, lessContent)
destroy: -> @unsubscribe()
+147 -106
Ver Arquivo
@@ -4,6 +4,7 @@ React = require 'react-atom-fork'
{debounce, isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus'
{$$} = require 'space-pen'
Decoration = require './decoration'
CursorsComponent = require './cursors-component'
HighlightsComponent = require './highlights-component'
OverlayManager = require './overlay-manager'
@@ -17,26 +18,33 @@ LinesComponent = React.createClass
displayName: 'LinesComponent'
render: ->
{editor, presenter} = @props
@oldState ?= {lines: {}}
@newState = presenter.state.content
{performedInitialMeasurement, cursorBlinkPeriod, cursorBlinkResumeDelay} = @props
{scrollHeight, scrollWidth, backgroundColor, placeholderText} = @newState
style =
height: scrollHeight
width: scrollWidth
WebkitTransform: @getTransform()
backgroundColor: backgroundColor
if performedInitialMeasurement
{editor, overlayDecorations, highlightDecorations, scrollHeight, scrollWidth, placeholderText, backgroundColor} = @props
{lineHeightInPixels, defaultCharWidth, scrollViewHeight, scopedCharacterWidthsChangeCount} = @props
{scrollTop, scrollLeft, cursorPixelRects} = @props
style =
height: Math.max(scrollHeight, scrollViewHeight)
width: scrollWidth
WebkitTransform: @getTransform()
backgroundColor: if editor.isMini() then null else backgroundColor
div {className: 'lines', style},
div className: 'placeholder-text', placeholderText if placeholderText?
CursorsComponent {presenter}
HighlightsComponent {presenter}
CursorsComponent {
cursorPixelRects, cursorBlinkPeriod, cursorBlinkResumeDelay, lineHeightInPixels,
defaultCharWidth, scopedCharacterWidthsChangeCount, performedInitialMeasurement
}
HighlightsComponent {
editor, highlightDecorations, lineHeightInPixels, defaultCharWidth,
scopedCharacterWidthsChangeCount, performedInitialMeasurement
}
getTransform: ->
{scrollTop, scrollLeft} = @newState
{useHardwareAcceleration} = @props
{scrollTop, scrollLeft, useHardwareAcceleration} = @props
if useHardwareAcceleration
"translate3d(#{-scrollLeft}px, #{-scrollTop}px, 0px)"
@@ -44,7 +52,7 @@ LinesComponent = React.createClass
"translate(#{-scrollLeft}px, #{-scrollTop}px)"
componentWillMount: ->
@measuredLines = new Set
@measuredLines = new WeakSet
@lineNodesByLineId = {}
@screenRowsByLineId = {}
@lineIdsByScreenRow = {}
@@ -63,91 +71,124 @@ LinesComponent = React.createClass
else
@overlayManager = new OverlayManager(@getDOMNode())
componentDidUpdate: ->
{visible, presenter} = @props
shouldComponentUpdate: (newProps) ->
return true unless isEqualForProperties(newProps, @props,
'renderedRowRange', 'lineDecorations', 'highlightDecorations', 'lineHeightInPixels', 'defaultCharWidth',
'overlayDecorations', 'scrollTop', 'scrollLeft', 'showIndentGuide', 'scrollingVertically', 'visible',
'scrollViewHeight', 'mouseWheelScreenRow', 'scopedCharacterWidthsChangeCount', 'lineWidth', 'useHardwareAcceleration',
'placeholderText', 'performedInitialMeasurement', 'backgroundColor', 'cursorPixelRects'
)
@removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible
@updateLineNodes()
@measureCharactersInNewLines() if visible and not @newState.scrollingVertically
{renderedRowRange, pendingChanges} = newProps
return false unless renderedRowRange?
[renderedStartRow, renderedEndRow] = renderedRowRange
for change in pendingChanges
if change.screenDelta is 0
return true unless change.end < renderedStartRow or renderedEndRow <= change.start
else
return true unless renderedEndRow <= change.start
false
componentDidUpdate: (prevProps) ->
{visible, scrollingVertically, performedInitialMeasurement} = @props
return unless performedInitialMeasurement
@clearScreenRowCaches() unless prevProps.lineHeightInPixels is @props.lineHeightInPixels
@removeLineNodes() unless isEqualForProperties(prevProps, @props, 'showIndentGuide')
@updateLines(@props.lineWidth isnt prevProps.lineWidth)
@measureCharactersInNewLines() if visible and not scrollingVertically
@overlayManager?.render(@props)
@oldState.indentGuidesVisible = @newState.indentGuidesVisible
@oldState.scrollWidth = @newState.scrollWidth
clearScreenRowCaches: ->
@screenRowsByLineId = {}
@lineIdsByScreenRow = {}
removeLineNodes: ->
@removeLineNode(id) for id of @oldState.lines
updateLines: (updateWidth) ->
{tokenizedLines, renderedRowRange, showIndentGuide, selectionChanged, lineDecorations} = @props
[startRow] = renderedRowRange
removeLineNode: (id) ->
@lineNodesByLineId[id].remove()
delete @lineNodesByLineId[id]
delete @lineIdsByScreenRow[@screenRowsByLineId[id]]
delete @screenRowsByLineId[id]
delete @oldState.lines[id]
@removeLineNodes(tokenizedLines)
@appendOrUpdateVisibleLineNodes(tokenizedLines, startRow, updateWidth)
updateLineNodes: ->
{presenter} = @props
removeLineNodes: (visibleLines=[]) ->
{mouseWheelScreenRow} = @props
visibleLineIds = new Set
visibleLineIds.add(line.id.toString()) for line in visibleLines
node = @getDOMNode()
for lineId, lineNode of @lineNodesByLineId when not visibleLineIds.has(lineId)
screenRow = @screenRowsByLineId[lineId]
if not screenRow? or screenRow isnt mouseWheelScreenRow
delete @lineNodesByLineId[lineId]
delete @lineIdsByScreenRow[screenRow] if @lineIdsByScreenRow[screenRow] is lineId
delete @screenRowsByLineId[lineId]
delete @renderedDecorationsByLineId[lineId]
node.removeChild(lineNode)
for id of @oldState.lines
unless @newState.lines.hasOwnProperty(id)
@removeLineNode(id)
appendOrUpdateVisibleLineNodes: (visibleLines, startRow, updateWidth) ->
{lineDecorations} = @props
newLineIds = null
newLines = null
newLinesHTML = null
for id, lineState of @newState.lines
if @oldState.lines.hasOwnProperty(id)
@updateLineNode(id)
else
newLineIds ?= []
newLinesHTML ?= ""
newLineIds.push(id)
newLinesHTML += @buildLineHTML(id)
@screenRowsByLineId[id] = lineState.screenRow
@lineIdsByScreenRow[lineState.screenRow] = id
@oldState.lines[id] = _.clone(lineState)
for line, index in visibleLines
screenRow = startRow + index
return unless newLineIds?
if @hasLineNode(line.id)
@updateLineNode(line, screenRow, updateWidth)
else
newLines ?= []
newLinesHTML ?= ""
newLines.push(line)
newLinesHTML += @buildLineHTML(line, screenRow)
@screenRowsByLineId[line.id] = screenRow
@lineIdsByScreenRow[screenRow] = line.id
@renderedDecorationsByLineId[line.id] = lineDecorations[screenRow]
return unless newLines?
WrapperDiv.innerHTML = newLinesHTML
newLineNodes = toArray(WrapperDiv.children)
node = @getDOMNode()
for id, i in newLineIds
for line, i in newLines
lineNode = newLineNodes[i]
@lineNodesByLineId[id] = lineNode
@lineNodesByLineId[line.id] = lineNode
node.appendChild(lineNode)
buildLineHTML: (id) ->
{presenter} = @props
{scrollWidth} = @newState
{screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newState.lines[id]
hasLineNode: (lineId) ->
@lineNodesByLineId.hasOwnProperty(lineId)
buildLineHTML: (line, screenRow) ->
{showIndentGuide, lineHeightInPixels, lineDecorations, lineWidth} = @props
{tokens, text, lineEnding, fold, isSoftWrapped, indentLevel} = line
classes = ''
if decorationClasses?
for decorationClass in decorationClasses
classes += decorationClass + ' '
if decorations = lineDecorations[screenRow]
for id, decoration of decorations
if Decoration.isType(decoration, 'line')
classes += decoration.class + ' '
classes += 'line'
lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px; width: #{scrollWidth}px;\" data-screen-row=\"#{screenRow}\">"
top = screenRow * lineHeightInPixels
lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px; width: #{lineWidth}px;\" data-screen-row=\"#{screenRow}\">"
if text is ""
lineHTML += @buildEmptyLineInnerHTML(id)
lineHTML += @buildEmptyLineInnerHTML(line)
else
lineHTML += @buildLineInnerHTML(id)
lineHTML += @buildLineInnerHTML(line)
lineHTML += '<span class="fold-marker"></span>' if fold
lineHTML += "</div>"
lineHTML
buildEmptyLineInnerHTML: (id) ->
{indentGuidesVisible} = @newState
{indentLevel, tabLength, endOfLineInvisibles} = @newState.lines[id]
buildEmptyLineInnerHTML: (line) ->
{showIndentGuide} = @props
{indentLevel, tabLength, endOfLineInvisibles} = line
if indentGuidesVisible and indentLevel > 0
if showIndentGuide and indentLevel > 0
invisibleIndex = 0
lineHTML = ''
for i in [0...indentLevel]
@@ -160,30 +201,30 @@ LinesComponent = React.createClass
lineHTML += "</span>"
while invisibleIndex < endOfLineInvisibles?.length
lineHTML += "<span class='invisible-character'>#{endOfLineInvisibles[invisibleIndex++]}</span>"
lineHTML += "<span class='invisible-character'>#{line.endOfLineInvisibles[invisibleIndex++]}</span>"
lineHTML
else
@buildEndOfLineHTML(id) or '&nbsp;'
@buildEndOfLineHTML(line) or '&nbsp;'
buildLineInnerHTML: (id) ->
{editor} = @props
{indentGuidesVisible} = @newState
{tokens, text, isOnlyWhitespace} = @newState.lines[id]
buildLineInnerHTML: (line) ->
{editor, showIndentGuide} = @props
{tokens, text} = line
innerHTML = ""
scopeStack = []
lineIsWhitespaceOnly = line.isOnlyWhitespace()
for token in tokens
innerHTML += @updateScopeStack(scopeStack, token.scopes)
hasIndentGuide = indentGuidesVisible and (token.hasLeadingWhitespace() or (token.hasTrailingWhitespace() and isOnlyWhitespace))
hasIndentGuide = not editor.isMini() and showIndentGuide and (token.hasLeadingWhitespace() or (token.hasTrailingWhitespace() and lineIsWhitespaceOnly))
innerHTML += token.getValueAsHtml({hasIndentGuide})
innerHTML += @popScope(scopeStack) while scopeStack.length > 0
innerHTML += @buildEndOfLineHTML(id)
innerHTML += @buildEndOfLineHTML(line)
innerHTML
buildEndOfLineHTML: (id) ->
{endOfLineInvisibles} = @newState.lines[id]
buildEndOfLineHTML: (line) ->
{endOfLineInvisibles} = line
html = ''
if endOfLineInvisibles?
@@ -216,30 +257,33 @@ LinesComponent = React.createClass
scopeStack.push(scope)
"<span class=\"#{scope.replace(/\.+/g, ' ')}\">"
updateLineNode: (id) ->
{scrollWidth} = @newState
{screenRow, top} = @newState.lines[id]
updateLineNode: (line, screenRow, updateWidth) ->
{lineHeightInPixels, lineDecorations, lineWidth} = @props
lineNode = @lineNodesByLineId[line.id]
lineNode = @lineNodesByLineId[id]
decorations = lineDecorations[screenRow]
previousDecorations = @renderedDecorationsByLineId[line.id]
newDecorationClasses = @newState.lines[id].decorationClasses
oldDecorationClasses = @oldState.lines[id].decorationClasses
if previousDecorations?
for id, decoration of previousDecorations
if Decoration.isType(decoration, 'line') and not @hasDecoration(decorations, decoration)
lineNode.classList.remove(decoration.class)
if oldDecorationClasses?
for decorationClass in oldDecorationClasses
unless newDecorationClasses? and decorationClass in newDecorationClasses
lineNode.classList.remove(decorationClass)
if decorations?
for id, decoration of decorations
if Decoration.isType(decoration, 'line') and not @hasDecoration(previousDecorations, decoration)
lineNode.classList.add(decoration.class)
if newDecorationClasses?
for decorationClass in newDecorationClasses
unless oldDecorationClasses? and decorationClass in oldDecorationClasses
lineNode.classList.add(decorationClass)
lineNode.style.width = lineWidth + 'px' if updateWidth
lineNode.style.width = scrollWidth + 'px'
lineNode.style.top = top + 'px'
lineNode.dataset.screenRow = screenRow
@screenRowsByLineId[id] = screenRow
@lineIdsByScreenRow[screenRow] = id
unless @screenRowsByLineId[line.id] is screenRow
lineNode.style.top = screenRow * lineHeightInPixels + 'px'
lineNode.dataset.screenRow = screenRow
@screenRowsByLineId[line.id] = screenRow
@lineIdsByScreenRow[screenRow] = line.id
hasDecoration: (decorations, decoration) ->
decorations? and decorations[decoration.id] is decoration
lineNodeForScreenRow: (screenRow) ->
@lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
@@ -251,27 +295,26 @@ LinesComponent = React.createClass
charWidth = DummyLineNode.firstChild.getBoundingClientRect().width
node.removeChild(DummyLineNode)
{editor, presenter} = @props
presenter.setLineHeight(lineHeightInPixels)
{editor} = @props
editor.setLineHeightInPixels(lineHeightInPixels)
presenter.setBaseCharacterWidth(charWidth)
editor.setDefaultCharWidth(charWidth)
remeasureCharacterWidths: ->
return unless @props.presenter.hasRequiredMeasurements()
return unless @props.performedInitialMeasurement
@clearScopedCharWidths()
@measureCharactersInNewLines()
measureCharactersInNewLines: ->
{editor} = @props
{editor, tokenizedLines, renderedRowRange} = @props
[visibleStartRow] = renderedRowRange
node = @getDOMNode()
editor.batchCharacterMeasurement =>
for id, lineState of @oldState.lines
unless @measuredLines.has(id)
lineNode = @lineNodesByLineId[id]
@measureCharactersInLine(lineState, lineNode)
for tokenizedLine in tokenizedLines
unless @measuredLines.has(tokenizedLine)
lineNode = @lineNodesByLineId[tokenizedLine.id]
@measureCharactersInLine(tokenizedLine, lineNode)
return
measureCharactersInLine: (tokenizedLine, lineNode) ->
@@ -314,13 +357,11 @@ LinesComponent = React.createClass
rangeForMeasurement.setEnd(textNode, i + charLength)
charWidth = rangeForMeasurement.getBoundingClientRect().width
editor.setScopedCharWidth(scopes, char, charWidth)
@props.presenter.setScopedCharWidth(scopes, char, charWidth)
charIndex += charLength
@measuredLines.add(tokenizedLine.id)
@measuredLines.add(tokenizedLine)
clearScopedCharWidths: ->
@measuredLines.clear()
@props.editor.clearScopedCharWidths()
@props.presenter.clearScopedCharWidths()
+28 -23
Ver Arquivo
@@ -1,41 +1,46 @@
module.exports =
class OverlayManager
constructor: (@container) ->
@overlayNodesById = {}
@overlays = {}
render: (props) ->
{presenter} = props
{editor, overlayDecorations, lineHeightInPixels} = props
for decorationId, {pixelPosition, item} of presenter.state.content.overlays
@renderOverlay(presenter, decorationId, item, pixelPosition)
existingDecorations = null
for markerId, {headPixelPosition, tailPixelPosition, decorations} of overlayDecorations
for decoration in decorations
pixelPosition =
if decoration.position is 'tail' then tailPixelPosition else headPixelPosition
for id, overlayNode of @overlayNodesById
unless presenter.state.content.overlays.hasOwnProperty(id)
overlayNode.remove()
delete @overlayNodesById[id]
@renderOverlay(editor, decoration, pixelPosition, lineHeightInPixels)
existingDecorations ?= {}
existingDecorations[decoration.id] = true
for id, overlay of @overlays
unless existingDecorations? and id of existingDecorations
@container.removeChild(overlay)
delete @overlays[id]
return
renderOverlay: (presenter, decorationId, item, pixelPosition) ->
item = atom.views.getView(item)
unless overlayNode = @overlayNodesById[decorationId]
overlayNode = @overlayNodesById[decorationId] = document.createElement('atom-overlay')
overlayNode.appendChild(item)
@container.appendChild(overlayNode)
renderOverlay: (editor, decoration, pixelPosition, lineHeightInPixels) ->
item = atom.views.getView(decoration.item)
unless overlay = @overlays[decoration.id]
overlay = @overlays[decoration.id] = document.createElement('atom-overlay')
overlay.appendChild(item)
@container.appendChild(overlay)
itemWidth = item.offsetWidth
itemHeight = item.offsetHeight
{scrollTop, scrollLeft} = presenter.state.content
left = pixelPosition.left
if left + itemWidth - scrollLeft > presenter.contentFrameWidth and left - itemWidth >= scrollLeft
if left + itemWidth - editor.getScrollLeft() > editor.getWidth() and left - itemWidth >= editor.getScrollLeft()
left -= itemWidth
top = pixelPosition.top + presenter.lineHeight
if top + itemHeight - scrollTop > presenter.computeHeight() and top - itemHeight - presenter.lineHeight >= scrollTop
top -= itemHeight + presenter.lineHeight
top = pixelPosition.top + lineHeightInPixels
if top + itemHeight - editor.getScrollTop() > editor.getHeight() and top - itemHeight - lineHeightInPixels >= editor.getScrollTop()
top -= itemHeight + lineHeightInPixels
overlayNode.style.top = top + 'px'
overlayNode.style.left = left + 'px'
overlay.style.top = top + 'px'
overlay.style.left = left + 'px'
+26 -18
Ver Arquivo
@@ -7,33 +7,28 @@ ScrollbarComponent = React.createClass
displayName: 'ScrollbarComponent'
render: ->
{presenter, orientation, className, useHardwareAcceleration} = @props
switch orientation
when 'vertical'
@newState = presenter.state.verticalScrollbar
when 'horizontal'
@newState = presenter.state.horizontalScrollbar
{orientation, className, scrollHeight, scrollWidth, visible} = @props
{scrollableInOppositeDirection, horizontalScrollbarHeight, verticalScrollbarWidth} = @props
{useHardwareAcceleration} = @props
style = {}
style.display = 'none' unless @newState.visible
style.display = 'none' unless visible
style.transform = 'translateZ(0)' if useHardwareAcceleration # See atom/atom#3559
switch orientation
when 'vertical'
style.width = @newState.width
style.bottom = @newState.bottom
style.width = verticalScrollbarWidth
style.bottom = horizontalScrollbarHeight if scrollableInOppositeDirection
when 'horizontal'
style.left = 0
style.right = @newState.right
style.height = @newState.height
style.right = verticalScrollbarWidth if scrollableInOppositeDirection
style.height = horizontalScrollbarHeight
div {className, style},
switch orientation
when 'vertical'
div className: 'scrollbar-content', style: {height: @newState.scrollHeight}
div className: 'scrollbar-content', style: {height: scrollHeight}
when 'horizontal'
div className: 'scrollbar-content', style: {width: @newState.scrollWidth}
div className: 'scrollbar-content', style: {width: scrollWidth}
componentDidMount: ->
{orientation} = @props
@@ -46,15 +41,26 @@ ScrollbarComponent = React.createClass
componentWillUnmount: ->
@getDOMNode().removeEventListener 'scroll', @onScroll
shouldComponentUpdate: (newProps) ->
return true if newProps.visible isnt @props.visible
switch @props.orientation
when 'vertical'
not isEqualForProperties(newProps, @props, 'scrollHeight', 'scrollTop', 'scrollableInOppositeDirection', 'verticalScrollbarWidth')
when 'horizontal'
not isEqualForProperties(newProps, @props, 'scrollWidth', 'scrollLeft', 'scrollableInOppositeDirection', 'horizontalScrollbarHeight')
componentDidUpdate: ->
{orientation} = @props
{orientation, scrollTop, scrollLeft} = @props
node = @getDOMNode()
switch orientation
when 'vertical'
node.scrollTop = @newState.scrollTop
node.scrollTop = scrollTop
@props.scrollTop = node.scrollTop # Ensure scrollTop reflects actual DOM without triggering another update
when 'horizontal'
node.scrollLeft = @newState.scrollLeft
node.scrollLeft = scrollLeft
@props.scrollLeft = node.scrollLeft # Ensure scrollLeft reflects actual DOM without triggering another update
onScroll: ->
{orientation, onScroll} = @props
@@ -63,7 +69,9 @@ ScrollbarComponent = React.createClass
switch orientation
when 'vertical'
scrollTop = node.scrollTop
@props.scrollTop = scrollTop # Ensure scrollTop reflects actual DOM without triggering another update
onScroll(scrollTop)
when 'horizontal'
scrollLeft = node.scrollLeft
@props.scrollLeft = scrollLeft # Ensure scrollLeft reflects actual DOM without triggering another update
onScroll(scrollLeft)
+4 -5
Ver Arquivo
@@ -7,11 +7,7 @@ ScrollbarCornerComponent = React.createClass
displayName: 'ScrollbarCornerComponent'
render: ->
{presenter, measuringScrollbars} = @props
visible = presenter.state.horizontalScrollbar.visible and presenter.state.verticalScrollbar.visible
width = presenter.state.verticalScrollbar.width
height = presenter.state.horizontalScrollbar.height
{visible, measuringScrollbars, width, height} = @props
if measuringScrollbars
height = 25
@@ -23,3 +19,6 @@ ScrollbarCornerComponent = React.createClass
div style:
height: height + 1
width: width + 1
shouldComponentUpdate: (newProps) ->
not isEqualForProperties(newProps, @props, 'measuringScrollbars', 'visible', 'width', 'height')
+253 -43
Ver Arquivo
@@ -8,7 +8,6 @@ grim = require 'grim'
{CompositeDisposable} = require 'event-kit'
ipc = require 'ipc'
TextEditorPresenter = require './text-editor-presenter'
GutterComponent = require './gutter-component'
InputComponent = require './input-component'
LinesComponent = require './lines-component'
@@ -22,6 +21,9 @@ TextEditorComponent = React.createClass
mixins: [SubscriberMixin]
visible: false
autoHeight: false
backgroundColor: null
gutterBackgroundColor: null
pendingScrollTop: null
pendingScrollLeft: null
selectOnMouseMove: false
@@ -30,9 +32,13 @@ TextEditorComponent = React.createClass
updateRequestedWhilePaused: false
cursorMoved: false
selectionChanged: false
scrollingVertically: false
mouseWheelScreenRow: null
mouseWheelScreenRowClearDelay: 150
scrollSensitivity: 0.4
heightAndWidthMeasurementRequested: false
inputEnabled: true
scopedCharacterWidthsChangeCount: null
domPollingInterval: 100
domPollingIntervalId: null
domPollingPaused: false
@@ -41,19 +47,46 @@ TextEditorComponent = React.createClass
remeasureCharacterWidthsWhenShown: false
render: ->
{focused, showLineNumbers} = @state
{focused, showIndentGuide, showLineNumbers, visible} = @state
{editor, cursorBlinkPeriod, cursorBlinkResumeDelay, hostElement, useShadowDOM} = @props
maxLineNumberDigits = editor.getLineCount().toString().length
hasSelection = editor.getLastSelection()? and !editor.getLastSelection().isEmpty()
style = {}
@performedInitialMeasurement = false if editor.isDestroyed()
if @performedInitialMeasurement
renderedRowRange = @getRenderedRowRange()
[renderedStartRow, renderedEndRow] = renderedRowRange
cursorPixelRects = @getCursorPixelRects(renderedRowRange)
tokenizedLines = editor.tokenizedLinesForScreenRows(renderedStartRow, renderedEndRow - 1)
decorations = editor.decorationsForScreenRowRange(renderedStartRow, renderedEndRow)
highlightDecorations = @getHighlightDecorations(decorations)
overlayDecorations = @getOverlayDecorations(decorations)
lineDecorations = @getLineDecorations(decorations)
placeholderText = editor.getPlaceholderText() if editor.isEmpty()
visible = @isVisible()
scrollHeight = editor.getScrollHeight()
scrollWidth = editor.getScrollWidth()
scrollTop = editor.getScrollTop()
scrollLeft = editor.getScrollLeft()
lineHeightInPixels = editor.getLineHeightInPixels()
defaultCharWidth = editor.getDefaultCharWidth()
scrollViewHeight = editor.getHeight()
lineWidth = Math.max(scrollWidth, editor.getWidth())
horizontalScrollbarHeight = editor.getHorizontalScrollbarHeight()
verticalScrollbarWidth = editor.getVerticalScrollbarWidth()
verticallyScrollable = editor.verticallyScrollable()
horizontallyScrollable = editor.horizontallyScrollable()
hiddenInputStyle = @getHiddenInputPosition()
hiddenInputStyle.WebkitTransform = 'translateZ(0)' if @useHardwareAcceleration
style.height = @presenter.state.height if @presenter.state.height?
if @mouseWheelScreenRow? and not (renderedStartRow <= @mouseWheelScreenRow < renderedEndRow)
mouseWheelScreenRow = @mouseWheelScreenRow
style.height = scrollViewHeight if @autoHeight
if useShadowDOM
className = 'editor-contents--private'
@@ -65,8 +98,10 @@ TextEditorComponent = React.createClass
div {className, style},
if @gutterVisible
GutterComponent {
ref: 'gutter', onMouseDown: @onGutterMouseDown,
@presenter, editor, @useHardwareAcceleration
ref: 'gutter', onMouseDown: @onGutterMouseDown, lineDecorations,
defaultCharWidth, editor, renderedRowRange, maxLineNumberDigits, scrollViewHeight,
scrollTop, scrollHeight, lineHeightInPixels, @pendingChanges, mouseWheelScreenRow,
@useHardwareAcceleration, @performedInitialMeasurement, @backgroundColor, @gutterBackgroundColor
}
div ref: 'scrollView', className: 'scroll-view',
@@ -76,30 +111,53 @@ TextEditorComponent = React.createClass
style: hiddenInputStyle
LinesComponent {
ref: 'lines', @presenter, editor, hostElement, @useHardwareAcceleration, useShadowDOM, visible
ref: 'lines',
editor, lineHeightInPixels, defaultCharWidth, tokenizedLines,
lineDecorations, highlightDecorations, overlayDecorations, hostElement,
showIndentGuide, renderedRowRange, @pendingChanges, scrollTop, scrollLeft,
@scrollingVertically, scrollHeight, scrollWidth, mouseWheelScreenRow,
visible, scrollViewHeight, @scopedCharacterWidthsChangeCount, lineWidth, @useHardwareAcceleration,
placeholderText, @performedInitialMeasurement, @backgroundColor, cursorPixelRects,
cursorBlinkPeriod, cursorBlinkResumeDelay, useShadowDOM
}
ScrollbarComponent
ref: 'horizontalScrollbar'
className: 'horizontal-scrollbar'
orientation: 'horizontal'
presenter: @presenter
onScroll: @onHorizontalScroll
scrollLeft: scrollLeft
scrollWidth: scrollWidth
visible: horizontallyScrollable
scrollableInOppositeDirection: verticallyScrollable
verticalScrollbarWidth: verticalScrollbarWidth
horizontalScrollbarHeight: horizontalScrollbarHeight
useHardwareAcceleration: @useHardwareAcceleration
ScrollbarComponent
ref: 'verticalScrollbar'
className: 'vertical-scrollbar'
orientation: 'vertical'
presenter: @presenter
onScroll: @onVerticalScroll
scrollTop: scrollTop
scrollHeight: scrollHeight
visible: verticallyScrollable
scrollableInOppositeDirection: horizontallyScrollable
verticalScrollbarWidth: verticalScrollbarWidth
horizontalScrollbarHeight: horizontalScrollbarHeight
useHardwareAcceleration: @useHardwareAcceleration
# Also used to measure the height/width of scrollbars after the initial render
ScrollbarCornerComponent
ref: 'scrollbarCorner'
presenter: @presenter
visible: horizontallyScrollable and verticallyScrollable
measuringScrollbars: @measuringScrollbars
height: horizontalScrollbarHeight
width: verticalScrollbarWidth
getPageRows: ->
{editor} = @props
Math.max(1, Math.ceil(editor.getHeight() / editor.getLineHeightInPixels()))
getInitialState: -> {}
@@ -109,23 +167,11 @@ TextEditorComponent = React.createClass
lineOverdrawMargin: 15
componentWillMount: ->
@pendingChanges = []
@props.editor.manageScrollPosition = true
@observeConfig()
@setScrollSensitivity(atom.config.get('editor.scrollSensitivity'))
{editor, lineOverdrawMargin, cursorBlinkPeriod, cursorBlinkResumeDelay} = @props
@presenter = new TextEditorPresenter
model: editor
scrollTop: editor.getScrollTop()
scrollLeft: editor.getScrollLeft()
lineOverdrawMargin: lineOverdrawMargin
cursorBlinkPeriod: cursorBlinkPeriod
cursorBlinkResumeDelay: cursorBlinkResumeDelay
stoppedScrollingDelay: 200
@presenter.onDidUpdateState(@requestUpdate)
componentDidMount: ->
{editor, stylesElement} = @props
@@ -156,6 +202,7 @@ TextEditorComponent = React.createClass
componentDidUpdate: (prevProps, prevState) ->
cursorMoved = @cursorMoved
selectionChanged = @selectionChanged
@pendingChanges.length = 0
@cursorMoved = false
@selectionChanged = false
@@ -168,10 +215,10 @@ TextEditorComponent = React.createClass
becameVisible: ->
@updatesPaused = true
@measureScrollbars() if @measureScrollbarsWhenShown
@sampleFontStyling()
@sampleBackgroundColors()
@measureHeightAndWidth()
@measureScrollbars() if @measureScrollbarsWhenShown
@measureLineHeightAndDefaultCharWidth() if @measureLineHeightAndDefaultCharWidthWhenShown
@remeasureCharacterWidths() if @remeasureCharacterWidthsWhenShown
@props.editor.setVisible(true)
@@ -210,6 +257,13 @@ TextEditorComponent = React.createClass
getTopmostDOMNode: ->
@props.hostElement
getRenderedRowRange: ->
{editor, lineOverdrawMargin} = @props
[visibleStartRow, visibleEndRow] = editor.getVisibleRowRange()
renderedStartRow = Math.max(0, visibleStartRow - lineOverdrawMargin)
renderedEndRow = Math.min(editor.getScreenLineCount(), visibleEndRow + lineOverdrawMargin)
[renderedStartRow, renderedEndRow]
getHiddenInputPosition: ->
{editor} = @props
{focused} = @state
@@ -223,13 +277,117 @@ TextEditorComponent = React.createClass
left = Math.max(0, Math.min(editor.getWidth() - width, left))
{top, left}
getCursorScreenRanges: (renderedRowRange) ->
{editor} = @props
[renderedStartRow, renderedEndRow] = renderedRowRange
cursorScreenRanges = {}
for selection in editor.getSelections() when selection.isEmpty()
{cursor} = selection
screenRange = cursor.getScreenRange()
if renderedStartRow <= screenRange.start.row < renderedEndRow
cursorScreenRanges[cursor.id] = screenRange
cursorScreenRanges
getCursorPixelRects: (renderedRowRange) ->
{editor} = @props
[renderedStartRow, renderedEndRow] = renderedRowRange
cursorPixelRects = {}
for selection in editor.getSelections() when selection.isEmpty()
{cursor} = selection
screenRange = cursor.getScreenRange()
if renderedStartRow <= screenRange.start.row < renderedEndRow
cursorPixelRects[cursor.id] = editor.pixelRectForScreenRange(screenRange)
cursorPixelRects
getLineDecorations: (decorationsByMarkerId) ->
{editor} = @props
return {} if editor.isMini()
decorationsByScreenRow = {}
for markerId, decorations of decorationsByMarkerId
marker = editor.getMarker(markerId)
screenRange = null
headScreenRow = null
if marker.isValid()
for decoration in decorations
if decoration.isType('line-number') or decoration.isType('line')
decorationParams = decoration.getProperties()
screenRange ?= marker.getScreenRange()
headScreenRow ?= marker.getHeadScreenPosition().row
startRow = screenRange.start.row
endRow = screenRange.end.row
endRow-- if not screenRange.isEmpty() and screenRange.end.column == 0
for screenRow in [startRow..endRow]
continue if decorationParams.onlyHead and screenRow isnt headScreenRow
if screenRange.isEmpty()
continue if decorationParams.onlyNonEmpty
else
continue if decorationParams.onlyEmpty
decorationsByScreenRow[screenRow] ?= {}
decorationsByScreenRow[screenRow][decoration.id] = decorationParams
decorationsByScreenRow
getHighlightDecorations: (decorationsByMarkerId) ->
{editor} = @props
filteredDecorations = {}
for markerId, decorations of decorationsByMarkerId
marker = editor.getMarker(markerId)
screenRange = marker.getScreenRange()
if marker.isValid() and not screenRange.isEmpty()
for decoration in decorations
if decoration.isType('highlight')
decorationParams = decoration.getProperties()
filteredDecorations[markerId] ?=
id: markerId
startPixelPosition: editor.pixelPositionForScreenPosition(screenRange.start, true)
endPixelPosition: editor.pixelPositionForScreenPosition(screenRange.end, true)
decorations: []
filteredDecorations[markerId].decorations.push decorationParams
filteredDecorations
getOverlayDecorations: (decorationsByMarkerId) ->
{editor} = @props
filteredDecorations = {}
for markerId, decorations of decorationsByMarkerId
marker = editor.getMarker(markerId)
headScreenPosition = marker.getHeadScreenPosition()
tailScreenPosition = marker.getTailScreenPosition()
if marker.isValid()
for decoration in decorations
if decoration.isType('overlay')
decorationParams = decoration.getProperties()
filteredDecorations[markerId] ?=
id: markerId
headPixelPosition: editor.pixelPositionForScreenPosition(headScreenPosition, true)
tailPixelPosition: editor.pixelPositionForScreenPosition(tailScreenPosition, true)
decorations: []
filteredDecorations[markerId].decorations.push decorationParams
filteredDecorations
observeEditor: ->
{editor} = @props
@subscribe editor.onDidChange(@onScreenLinesChanged)
@subscribe editor.onDidChangeGutterVisible(@updateGutterVisible)
@subscribe editor.onDidChangeMini(@setMini)
@subscribe editor.observeGrammar(@onGrammarChanged)
@subscribe editor.observeCursors(@onCursorAdded)
@subscribe editor.observeSelections(@onSelectionAdded)
@subscribe editor.observeDecorations(@onDecorationAdded)
@subscribe editor.onDidRemoveDecoration(@onDecorationRemoved)
@subscribe editor.onDidChangeCharacterWidths(@onCharacterWidthsChanged)
@subscribe editor.onDidChangePlaceholderText(@onPlaceholderTextChanged)
@subscribe editor.$scrollTop.changes, @onScrollTopChanged
@subscribe editor.$scrollLeft.changes, @requestUpdate
@subscribe editor.$verticalScrollbarWidth.changes, @requestUpdate
@subscribe editor.$horizontalScrollbarHeight.changes, @requestUpdate
@subscribe editor.$height.changes, @requestUpdate
@subscribe editor.$width.changes, @requestUpdate
@subscribe editor.$defaultCharWidth.changes, @requestUpdate
@subscribe editor.$lineHeightInPixels.changes, @requestUpdate
listenForDOMEvents: ->
node = @getDOMNode()
@@ -300,7 +458,7 @@ TextEditorComponent = React.createClass
scopeDescriptor = editor.getRootScopeDescriptor()
subscriptions.add atom.config.observe 'editor.showIndentGuide', scope: scopeDescriptor, @requestUpdate
subscriptions.add atom.config.observe 'editor.showIndentGuide', scope: scopeDescriptor, @setShowIndentGuide
subscriptions.add atom.config.observe 'editor.showLineNumbers', scope: scopeDescriptor, @updateGutterVisible
subscriptions.add atom.config.observe 'editor.scrollSensitivity', scope: scopeDescriptor, @setScrollSensitivity
@@ -347,7 +505,7 @@ TextEditorComponent = React.createClass
@requestAnimationFrame =>
pendingScrollTop = @pendingScrollTop
@pendingScrollTop = null
@presenter.setScrollTop(pendingScrollTop)
@props.editor.setScrollTop(pendingScrollTop)
onHorizontalScroll: (scrollLeft) ->
{editor} = @props
@@ -358,7 +516,7 @@ TextEditorComponent = React.createClass
@pendingScrollLeft = scrollLeft
unless animationFramePending
@requestAnimationFrame =>
@presenter.setScrollLeft(@pendingScrollLeft)
@props.editor.setScrollLeft(@pendingScrollLeft)
@pendingScrollLeft = null
onMouseWheel: (event) ->
@@ -379,13 +537,15 @@ TextEditorComponent = React.createClass
if Math.abs(wheelDeltaX) > Math.abs(wheelDeltaY)
# Scrolling horizontally
previousScrollLeft = editor.getScrollLeft()
@presenter.setScrollLeft(previousScrollLeft - Math.round(wheelDeltaX * @scrollSensitivity))
editor.setScrollLeft(previousScrollLeft - Math.round(wheelDeltaX * @scrollSensitivity))
event.preventDefault() unless previousScrollLeft is editor.getScrollLeft()
else
# Scrolling vertically
@presenter.setMouseWheelScreenRow(@screenRowForNode(event.target))
previousScrollTop = @presenter.scrollTop
@presenter.setScrollTop(previousScrollTop - Math.round(wheelDeltaY * @scrollSensitivity))
@mouseWheelScreenRow = @screenRowForNode(event.target)
@clearMouseWheelScreenRowAfterDelay ?= debounce(@clearMouseWheelScreenRow, @mouseWheelScreenRowClearDelay)
@clearMouseWheelScreenRowAfterDelay()
previousScrollTop = editor.getScrollTop()
editor.setScrollTop(previousScrollTop - Math.round(wheelDeltaY * @scrollSensitivity))
event.preventDefault() unless previousScrollTop is editor.getScrollTop()
onScrollViewScroll: ->
@@ -522,6 +682,11 @@ TextEditorComponent = React.createClass
@sampleBackgroundColors()
@remeasureCharacterWidths()
onScreenLinesChanged: (change) ->
{editor} = @props
@pendingChanges.push(change)
@requestUpdate() if editor.intersectsVisibleRowRange(change.start, change.end + 1) # TODO: Use closed-open intervals for change events
onSelectionAdded: (selection) ->
{editor} = @props
@@ -540,6 +705,21 @@ TextEditorComponent = React.createClass
@selectionChanged = true
@requestUpdate()
onScrollTopChanged: ->
@scrollingVertically = true
@requestUpdate()
@onStoppedScrollingAfterDelay ?= debounce(@onStoppedScrolling, 200)
@onStoppedScrollingAfterDelay()
onStoppedScrolling: ->
return unless @isMounted()
@scrollingVertically = false
@mouseWheelScreenRow = null
@requestUpdate()
onStoppedScrollingAfterDelay: null # created lazily
onCursorAdded: (cursor) ->
@subscribe cursor.onDidChangePosition @onCursorMoved
@@ -547,6 +727,23 @@ TextEditorComponent = React.createClass
@cursorMoved = true
@requestUpdate()
onDecorationAdded: (decoration) ->
@subscribe decoration.onDidChangeProperties(@onDecorationChanged)
@subscribe decoration.getMarker().onDidChange(@onDecorationChanged)
@requestUpdate()
onDecorationChanged: ->
@requestUpdate()
onDecorationRemoved: ->
@requestUpdate()
onCharacterWidthsChanged: (@scopedCharacterWidthsChangeCount) ->
@requestUpdate()
onPlaceholderTextChanged: ->
@requestUpdate()
handleDragUntilMouseUp: (event, dragHandler) ->
{editor} = @props
dragging = false
@@ -643,19 +840,20 @@ TextEditorComponent = React.createClass
{height} = hostElement.style
if position is 'absolute' or height
@presenter.setAutoHeight(false)
height = hostElement.offsetHeight
if height > 0
@presenter.setExplicitHeight(height)
if @autoHeight
@autoHeight = false
@forceUpdate() if not @updatesPaused and @canUpdate()
clientHeight = scrollViewNode.clientHeight
editor.setHeight(clientHeight) if clientHeight > 0
else
@presenter.setAutoHeight(true)
@presenter.setExplicitHeight(null)
editor.setHeight(null)
@autoHeight = true
clientWidth = scrollViewNode.clientWidth
paddingLeft = parseInt(getComputedStyle(scrollViewNode).paddingLeft)
clientWidth -= paddingLeft
if clientWidth > 0
@presenter.setContentFrameWidth(clientWidth)
editor.setWidth(clientWidth) if clientWidth > 0
sampleFontStyling: ->
oldFontSize = @fontSize
@@ -672,13 +870,18 @@ TextEditorComponent = React.createClass
sampleBackgroundColors: (suppressUpdate) ->
{hostElement} = @props
{showLineNumbers} = @state
{backgroundColor} = getComputedStyle(hostElement)
@presenter.setBackgroundColor(backgroundColor)
if backgroundColor isnt @backgroundColor
@backgroundColor = backgroundColor
@requestUpdate() unless suppressUpdate
if @refs.gutter?
gutterBackgroundColor = getComputedStyle(@refs.gutter.getDOMNode()).backgroundColor
@presenter.setGutterBackgroundColor(gutterBackgroundColor)
if gutterBackgroundColor isnt @gutterBackgroundColor
@gutterBackgroundColor = gutterBackgroundColor
@requestUpdate() unless suppressUpdate
measureLineHeightAndDefaultCharWidth: ->
if @isVisible()
@@ -706,8 +909,8 @@ TextEditorComponent = React.createClass
width = (cornerNode.offsetWidth - cornerNode.clientWidth) or 15
height = (cornerNode.offsetHeight - cornerNode.clientHeight) or 15
@presenter.setVerticalScrollbarWidth(width)
@presenter.setHorizontalScrollbarHeight(height)
editor.setVerticalScrollbarWidth(width)
editor.setHorizontalScrollbarHeight(height)
cornerNode.style.display = originalDisplayValue
@@ -752,6 +955,13 @@ TextEditorComponent = React.createClass
horizontalNode.style.display = originalHorizontalDisplayValue
cornerNode.style.display = originalCornerDisplayValue
clearMouseWheelScreenRow: ->
if @mouseWheelScreenRow?
@mouseWheelScreenRow = null
@requestUpdate()
clearMouseWheelScreenRowAfterDelay: null # created lazily
consolidateSelections: (e) ->
e.abortKeyBinding() unless @props.editor.consolidateSelections()
@@ -785,7 +995,7 @@ TextEditorComponent = React.createClass
@sampleFontStyling()
setShowIndentGuide: (showIndentGuide) ->
atom.config.set("editor.showIndentGuide", showIndentGuide)
@setState({showIndentGuide})
setMini: ->
@updateGutterVisible()
-831
Ver Arquivo
@@ -1,831 +0,0 @@
{CompositeDisposable, Emitter} = require 'event-kit'
{Point, Range} = require 'text-buffer'
_ = require 'underscore-plus'
module.exports =
class TextEditorPresenter
toggleCursorBlinkHandle: null
startBlinkingCursorsAfterDelay: null
stoppedScrollingTimeoutId: null
mouseWheelScreenRow: null
constructor: (params) ->
{@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft} = params
{@horizontalScrollbarHeight, @verticalScrollbarWidth} = params
{@lineHeight, @baseCharacterWidth, @lineOverdrawMargin, @backgroundColor, @gutterBackgroundColor} = params
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay} = params
@disposables = new CompositeDisposable
@emitter = new Emitter
@charWidthsByScope = {}
@transferMeasurementsToModel()
@observeModel()
@observeConfig()
@buildState()
@startBlinkingCursors()
destroy: ->
@disposables.dispose()
onDidUpdateState: (callback) ->
@emitter.on 'did-update-state', callback
transferMeasurementsToModel: ->
@model.setHeight(@explicitHeight) if @explicitHeight?
@model.setWidth(@contentFrameWidth) if @contentFrameWidth?
@model.setLineHeightInPixels(@lineHeight) if @lineHeight?
@model.setDefaultCharWidth(@baseCharacterWidth) if @baseCharacterWidth?
@model.setScrollTop(@scrollTop) if @scrollTop?
@model.setScrollLeft(@scrollLeft) if @scrollLeft?
@model.setVerticalScrollbarWidth(@verticalScrollbarWidth) if @verticalScrollbarWidth?
@model.setHorizontalScrollbarHeight(@horizontalScrollbarHeight) if @horizontalScrollbarHeight?
observeModel: ->
@disposables.add @model.onDidChange =>
@updateHeightState()
@updateVerticalScrollState()
@updateHorizontalScrollState()
@updateScrollbarsState()
@updateContentState()
@updateDecorations()
@updateLinesState()
@updateGutterState()
@updateLineNumbersState()
@disposables.add @model.onDidChangeGrammar(@updateContentState.bind(this))
@disposables.add @model.onDidChangePlaceholderText(@updateContentState.bind(this))
@disposables.add @model.onDidChangeMini =>
@updateContentState()
@updateDecorations()
@updateLinesState()
@updateLineNumbersState()
@disposables.add @model.onDidAddDecoration(@didAddDecoration.bind(this))
@disposables.add @model.onDidAddCursor(@didAddCursor.bind(this))
@disposables.add @model.onDidChangeScrollTop(@setScrollTop.bind(this))
@disposables.add @model.onDidChangeScrollLeft(@setScrollLeft.bind(this))
@observeDecoration(decoration) for decoration in @model.getDecorations()
@observeCursor(cursor) for cursor in @model.getCursors()
observeConfig: ->
@disposables.add atom.config.onDidChange 'editor.showIndentGuide', scope: @model.getRootScopeDescriptor(), @updateContentState.bind(this)
buildState: ->
@state =
horizontalScrollbar: {}
verticalScrollbar: {}
content:
scrollingVertically: false
blinkCursorsOff: false
lines: {}
highlights: {}
overlays: {}
gutter:
lineNumbers: {}
@updateState()
updateState: ->
@updateHeightState()
@updateVerticalScrollState()
@updateHorizontalScrollState()
@updateScrollbarsState()
@updateContentState()
@updateDecorations()
@updateLinesState()
@updateCursorsState()
@updateOverlaysState()
@updateGutterState()
@updateLineNumbersState()
updateHeightState: ->
if @autoHeight
@state.height = @computeContentHeight()
else
@state.height = null
@emitter.emit 'did-update-state'
updateVerticalScrollState: ->
scrollHeight = @computeScrollHeight()
@state.content.scrollHeight = scrollHeight
@state.gutter.scrollHeight = scrollHeight
@state.verticalScrollbar.scrollHeight = scrollHeight
scrollTop = @computeScrollTop()
@state.content.scrollTop = scrollTop
@state.gutter.scrollTop = scrollTop
@state.verticalScrollbar.scrollTop = scrollTop
@emitter.emit 'did-update-state'
updateHorizontalScrollState: ->
scrollWidth = @computeScrollWidth()
@state.content.scrollWidth = scrollWidth
@state.horizontalScrollbar.scrollWidth = scrollWidth
scrollLeft = @computeScrollLeft()
@state.content.scrollLeft = scrollLeft
@state.horizontalScrollbar.scrollLeft = scrollLeft
@emitter.emit 'did-update-state'
updateScrollbarsState: ->
horizontalScrollbarHeight = @computeHorizontalScrollbarHeight()
verticalScrollbarWidth = @computeVerticalScrollbarWidth()
@state.horizontalScrollbar.visible = horizontalScrollbarHeight > 0
@state.horizontalScrollbar.height = @horizontalScrollbarHeight
@state.horizontalScrollbar.right = verticalScrollbarWidth
@state.verticalScrollbar.visible = verticalScrollbarWidth > 0
@state.verticalScrollbar.width = @verticalScrollbarWidth
@state.verticalScrollbar.bottom = horizontalScrollbarHeight
@emitter.emit 'did-update-state'
updateContentState: ->
@state.content.scrollWidth = @computeScrollWidth()
@state.content.scrollLeft = @scrollLeft
@state.content.indentGuidesVisible = not @model.isMini() and atom.config.get('editor.showIndentGuide', scope: @model.getRootScopeDescriptor())
@state.content.backgroundColor = if @model.isMini() then null else @backgroundColor
@state.content.placeholderText = if @model.isEmpty() then @model.getPlaceholderText() else null
@emitter.emit 'did-update-state'
updateLinesState: ->
return unless @hasRequiredMeasurements()
visibleLineIds = {}
startRow = @computeStartRow()
endRow = @computeEndRow()
row = startRow
while row < endRow
line = @model.tokenizedLineForScreenRow(row)
visibleLineIds[line.id] = true
if @state.content.lines.hasOwnProperty(line.id)
@updateLineState(row, line)
else
@buildLineState(row, line)
row++
if @mouseWheelScreenRow?
preservedLine = @model.tokenizedLineForScreenRow(@mouseWheelScreenRow)
visibleLineIds[preservedLine.id] = true
for id, line of @state.content.lines
unless visibleLineIds.hasOwnProperty(id)
delete @state.content.lines[id]
@emitter.emit 'did-update-state'
updateLineState: (row, line) ->
lineState = @state.content.lines[line.id]
lineState.screenRow = row
lineState.top = row * @lineHeight
lineState.decorationClasses = @lineDecorationClassesForRow(row)
buildLineState: (row, line) ->
@state.content.lines[line.id] =
screenRow: row
text: line.text
tokens: line.tokens
isOnlyWhitespace: line.isOnlyWhitespace()
endOfLineInvisibles: line.endOfLineInvisibles
indentLevel: line.indentLevel
tabLength: line.tabLength
fold: line.fold
top: row * @lineHeight
decorationClasses: @lineDecorationClassesForRow(row)
updateCursorsState: ->
@state.content.cursors = {}
return unless @hasRequiredMeasurements()
startRow = @computeStartRow()
endRow = @computeEndRow()
for cursor in @model.getCursors()
if cursor.isVisible() and startRow <= cursor.getScreenRow() < endRow
pixelRect = @pixelRectForScreenRange(cursor.getScreenRange())
pixelRect.width = @baseCharacterWidth if pixelRect.width is 0
@state.content.cursors[cursor.id] = pixelRect
@emitter.emit 'did-update-state'
updateOverlaysState: ->
return unless @hasRequiredMeasurements()
visibleDecorationIds = {}
for decoration in @model.getOverlayDecorations()
continue unless decoration.getMarker().isValid()
{item, position} = decoration.getProperties()
if position is 'tail'
screenPosition = decoration.getMarker().getTailScreenPosition()
else
screenPosition = decoration.getMarker().getHeadScreenPosition()
@state.content.overlays[decoration.id] ?= {item}
@state.content.overlays[decoration.id].pixelPosition = @pixelPositionForScreenPosition(screenPosition)
visibleDecorationIds[decoration.id] = true
for id of @state.content.overlays
delete @state.content.overlays[id] unless visibleDecorationIds[id]
@emitter.emit "did-update-state"
updateGutterState: ->
@state.gutter.maxLineNumberDigits = @model.getLineCount().toString().length
@state.gutter.backgroundColor = if @gutterBackgroundColor isnt "rgba(0, 0, 0, 0)"
@gutterBackgroundColor
else
@backgroundColor
@emitter.emit "did-update-state"
updateLineNumbersState: ->
startRow = @computeStartRow()
endRow = @computeEndRow()
visibleLineNumberIds = {}
if startRow > 0
rowBeforeStartRow = startRow - 1
lastBufferRow = @model.bufferRowForScreenRow(rowBeforeStartRow)
wrapCount = rowBeforeStartRow - @model.screenRowForBufferRow(lastBufferRow)
else
lastBufferRow = null
wrapCount = 0
for bufferRow, i in @model.bufferRowsForScreenRows(startRow, endRow - 1)
if bufferRow is lastBufferRow
wrapCount++
id = bufferRow + '-' + wrapCount
softWrapped = true
else
id = bufferRow
wrapCount = 0
lastBufferRow = bufferRow
softWrapped = false
screenRow = startRow + i
top = screenRow * @lineHeight
decorationClasses = @lineNumberDecorationClassesForRow(screenRow)
foldable = @model.isFoldableAtScreenRow(screenRow)
@state.gutter.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable}
visibleLineNumberIds[id] = true
if @mouseWheelScreenRow?
bufferRow = @model.bufferRowForScreenRow(@mouseWheelScreenRow)
wrapCount = @mouseWheelScreenRow - @model.screenRowForBufferRow(bufferRow)
id = bufferRow
id += '-' + wrapCount if wrapCount > 0
visibleLineNumberIds[id] = true
for id of @state.gutter.lineNumbers
delete @state.gutter.lineNumbers[id] unless visibleLineNumberIds[id]
@emitter.emit 'did-update-state'
buildHighlightRegions: (screenRange) ->
lineHeightInPixels = @lineHeight
startPixelPosition = @pixelPositionForScreenPosition(screenRange.start, true)
endPixelPosition = @pixelPositionForScreenPosition(screenRange.end, true)
spannedRows = screenRange.end.row - screenRange.start.row + 1
if spannedRows is 1
[
top: startPixelPosition.top
height: lineHeightInPixels
left: startPixelPosition.left
width: endPixelPosition.left - startPixelPosition.left
]
else
regions = []
# First row, extending from selection start to the right side of screen
regions.push(
top: startPixelPosition.top
left: startPixelPosition.left
height: lineHeightInPixels
right: 0
)
# Middle rows, extending from left side to right side of screen
if spannedRows > 2
regions.push(
top: startPixelPosition.top + lineHeightInPixels
height: endPixelPosition.top - startPixelPosition.top - lineHeightInPixels
left: 0
right: 0
)
# Last row, extending from left side of screen to selection end
if screenRange.end.column > 0
regions.push(
top: endPixelPosition.top
height: lineHeightInPixels
left: 0
width: endPixelPosition.left
)
regions
computeStartRow: ->
startRow = Math.floor(@computeScrollTop() / @lineHeight) - @lineOverdrawMargin
Math.max(0, startRow)
computeEndRow: ->
startRow = Math.floor(@computeScrollTop() / @lineHeight)
visibleLinesCount = Math.ceil(@computeHeight() / @lineHeight) + 1
endRow = startRow + visibleLinesCount + @lineOverdrawMargin
Math.min(@model.getScreenLineCount(), endRow)
computeScrollWidth: ->
Math.max(@computeContentWidth(), @contentFrameWidth)
computeScrollHeight: ->
Math.max(@computeContentHeight(), @computeHeight())
computeContentWidth: ->
contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), Infinity]).left
contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width
contentWidth
computeContentHeight: ->
@lineHeight * @model.getScreenLineCount()
computeClientHeight: ->
@computeHeight() - @computeHorizontalScrollbarHeight()
computeClientWidth: ->
@contentFrameWidth - @computeVerticalScrollbarWidth()
computeScrollTop: ->
@scrollTop = @constrainScrollTop(@scrollTop)
constrainScrollTop: (scrollTop) ->
if @hasRequiredMeasurements()
Math.max(0, Math.min(scrollTop, @computeScrollHeight() - @computeClientHeight()))
else
Math.max(0, scrollTop) if scrollTop?
computeScrollLeft: ->
@scrollLeft = @constrainScrollLeft(@scrollLeft)
constrainScrollLeft: (scrollLeft) ->
if @hasRequiredMeasurements()
Math.max(0, Math.min(scrollLeft, @computeScrollWidth() - @computeClientWidth()))
else
Math.max(0, scrollLeft) if scrollLeft?
computeHorizontalScrollbarHeight: ->
contentWidth = @computeContentWidth()
contentHeight = @computeContentHeight()
clientWidthWithoutVerticalScrollbar = @contentFrameWidth
clientWidthWithVerticalScrollbar = clientWidthWithoutVerticalScrollbar - @verticalScrollbarWidth
clientHeightWithoutHorizontalScrollbar = @computeHeight()
clientHeightWithHorizontalScrollbar = clientHeightWithoutHorizontalScrollbar - @horizontalScrollbarHeight
horizontalScrollbarVisible =
contentWidth > clientWidthWithoutVerticalScrollbar or
contentWidth > clientWidthWithVerticalScrollbar and contentHeight > clientHeightWithoutHorizontalScrollbar
if horizontalScrollbarVisible
@horizontalScrollbarHeight
else
0
computeVerticalScrollbarWidth: ->
contentWidth = @computeContentWidth()
contentHeight = @computeContentHeight()
clientWidthWithoutVerticalScrollbar = @contentFrameWidth
clientWidthWithVerticalScrollbar = clientWidthWithoutVerticalScrollbar - @verticalScrollbarWidth
clientHeightWithoutHorizontalScrollbar = @computeHeight()
clientHeightWithHorizontalScrollbar = clientHeightWithoutHorizontalScrollbar - @horizontalScrollbarHeight
verticalScrollbarVisible =
contentHeight > clientHeightWithoutHorizontalScrollbar or
contentHeight > clientHeightWithHorizontalScrollbar and contentWidth > clientWidthWithoutVerticalScrollbar
if verticalScrollbarVisible
@verticalScrollbarWidth
else
0
lineDecorationClassesForRow: (row) ->
return null if @model.isMini()
decorationClasses = null
for id, decoration of @lineDecorationsByScreenRow[row]
decorationClasses ?= []
decorationClasses.push(decoration.getProperties().class)
decorationClasses
lineNumberDecorationClassesForRow: (row) ->
return null if @model.isMini()
decorationClasses = null
for id, decoration of @lineNumberDecorationsByScreenRow[row]
decorationClasses ?= []
decorationClasses.push(decoration.getProperties().class)
decorationClasses
getCursorBlinkPeriod: -> @cursorBlinkPeriod
getCursorBlinkResumeDelay: -> @cursorBlinkResumeDelay
hasRequiredMeasurements: ->
@lineHeight? and
@baseCharacterWidth? and
@scrollTop? and
@contentFrameWidth? and
@scrollLeft? and
@verticalScrollbarWidth? and
@horizontalScrollbarHeight?
setScrollTop: (scrollTop) ->
scrollTop = @constrainScrollTop(scrollTop)
unless @scrollTop is scrollTop
@scrollTop = scrollTop
@model.setScrollTop(scrollTop)
@didStartScrolling()
@updateVerticalScrollState()
@updateDecorations()
@updateLinesState()
@updateCursorsState()
@updateLineNumbersState()
didStartScrolling: ->
if @stoppedScrollingTimeoutId?
clearTimeout(@stoppedScrollingTimeoutId)
@stoppedScrollingTimeoutId = null
@stoppedScrollingTimeoutId = setTimeout(@didStopScrolling.bind(this), @stoppedScrollingDelay)
@state.content.scrollingVertically = true
@emitter.emit 'did-update-state'
didStopScrolling: ->
@state.content.scrollingVertically = false
if @mouseWheelScreenRow?
@mouseWheelScreenRow = null
@updateLinesState()
@updateLineNumbersState()
else
@emitter.emit 'did-update-state'
setScrollLeft: (scrollLeft) ->
scrollLeft = @constrainScrollLeft(scrollLeft)
unless @scrollLeft is scrollLeft
@scrollLeft = scrollLeft
@model.setScrollLeft(scrollLeft)
@updateHorizontalScrollState()
setHorizontalScrollbarHeight: (horizontalScrollbarHeight) ->
unless @horizontalScrollbarHeight is horizontalScrollbarHeight
@horizontalScrollbarHeight = horizontalScrollbarHeight
@model.setHorizontalScrollbarHeight(horizontalScrollbarHeight)
@updateScrollbarsState()
@updateVerticalScrollState()
setVerticalScrollbarWidth: (verticalScrollbarWidth) ->
unless @verticalScrollbarWidth is verticalScrollbarWidth
@verticalScrollbarWidth = verticalScrollbarWidth
@model.setVerticalScrollbarWidth(verticalScrollbarWidth)
@updateScrollbarsState()
@updateHorizontalScrollState()
setAutoHeight: (autoHeight) ->
unless @autoHeight is autoHeight
@autoHeight = autoHeight
@updateHeightState()
setExplicitHeight: (explicitHeight) ->
unless @explicitHeight is explicitHeight
@explicitHeight = explicitHeight
@model.setHeight(explicitHeight)
@updateVerticalScrollState()
@updateScrollbarsState()
@updateDecorations()
@updateLinesState()
@updateCursorsState()
@updateLineNumbersState()
computeHeight: ->
@explicitHeight ? @computeContentHeight()
setContentFrameWidth: (contentFrameWidth) ->
unless @contentFrameWidth is contentFrameWidth
@contentFrameWidth = contentFrameWidth
@model.setWidth(contentFrameWidth)
@updateVerticalScrollState()
@updateHorizontalScrollState()
@updateScrollbarsState()
@updateContentState()
@updateDecorations()
@updateLinesState()
setBackgroundColor: (backgroundColor) ->
unless @backgroundColor is backgroundColor
@backgroundColor = backgroundColor
@updateContentState()
setGutterBackgroundColor: (gutterBackgroundColor) ->
unless @gutterBackgroundColor is gutterBackgroundColor
@gutterBackgroundColor = gutterBackgroundColor
@updateGutterState()
setLineHeight: (lineHeight) ->
unless @lineHeight is lineHeight
@lineHeight = lineHeight
@updateHeightState()
@updateVerticalScrollState()
@updateDecorations()
@updateLinesState()
@updateCursorsState()
@updateLineNumbersState()
@updateOverlaysState()
setMouseWheelScreenRow: (mouseWheelScreenRow) ->
unless @mouseWheelScreenRow is mouseWheelScreenRow
@mouseWheelScreenRow = mouseWheelScreenRow
@didStartScrolling()
setBaseCharacterWidth: (baseCharacterWidth) ->
unless @baseCharacterWidth is baseCharacterWidth
@baseCharacterWidth = baseCharacterWidth
@model.setDefaultCharWidth(baseCharacterWidth)
@characterWidthsChanged()
getScopedCharWidth: (scopeNames, char) ->
@getScopedCharWidths(scopeNames)[char]
getScopedCharWidths: (scopeNames) ->
scope = @charWidthsByScope
for scopeName in scopeNames
scope[scopeName] ?= {}
scope = scope[scopeName]
scope.charWidths ?= {}
scope.charWidths
batchCharacterMeasurement: (fn) ->
oldChangeCount = @scopedCharacterWidthsChangeCount
@batchingCharacterMeasurement = true
fn()
@batchingCharacterMeasurement = false
@characterWidthsChanged() if oldChangeCount isnt @scopedCharacterWidthsChangeCount
setScopedCharWidth: (scopeNames, char, width) ->
@getScopedCharWidths(scopeNames)[char] = width
@scopedCharacterWidthsChangeCount++
@characterWidthsChanged() unless @batchingCharacterMeasurement
characterWidthsChanged: ->
@updateHorizontalScrollState()
@updateContentState()
@updateDecorations()
@updateLinesState()
@updateCursorsState()
@updateOverlaysState()
clearScopedCharWidths: ->
@charWidthsByScope = {}
pixelPositionForScreenPosition: (screenPosition, clip=true) ->
screenPosition = Point.fromObject(screenPosition)
screenPosition = @model.clipScreenPosition(screenPosition) if clip
targetRow = screenPosition.row
targetColumn = screenPosition.column
baseCharacterWidth = @baseCharacterWidth
top = targetRow * @lineHeight
left = 0
column = 0
for token in @model.tokenizedLineForScreenRow(targetRow).tokens
charWidths = @getScopedCharWidths(token.scopes)
valueIndex = 0
while valueIndex < token.value.length
if token.hasPairedCharacter
char = token.value.substr(valueIndex, 2)
charLength = 2
valueIndex += 2
else
char = token.value[valueIndex]
charLength = 1
valueIndex++
return {top, left} if column is targetColumn
left += charWidths[char] ? baseCharacterWidth unless char is '\0'
column += charLength
{top, left}
pixelRectForScreenRange: (screenRange) ->
if screenRange.end.row > screenRange.start.row
top = @pixelPositionForScreenPosition(screenRange.start).top
left = 0
height = (screenRange.end.row - screenRange.start.row + 1) * @lineHeight
width = @computeScrollWidth()
else
{top, left} = @pixelPositionForScreenPosition(screenRange.start, false)
height = @lineHeight
width = @pixelPositionForScreenPosition(screenRange.end, false).left - left
{top, left, width, height}
observeDecoration: (decoration) ->
decorationDisposables = new CompositeDisposable
decorationDisposables.add decoration.getMarker().onDidChange(@decorationMarkerDidChange.bind(this, decoration))
if decoration.isType('highlight')
decorationDisposables.add decoration.onDidChangeProperties(@updateHighlightState.bind(this, decoration))
decorationDisposables.add decoration.onDidFlash(@highlightDidFlash.bind(this, decoration))
decorationDisposables.add decoration.onDidDestroy =>
@disposables.remove(decorationDisposables)
decorationDisposables.dispose()
@didDestroyDecoration(decoration)
@disposables.add(decorationDisposables)
decorationMarkerDidChange: (decoration, change) ->
if decoration.isType('line') or decoration.isType('line-number')
intersectsVisibleRowRange = false
startRow = @computeStartRow()
endRow = @computeEndRow()
oldRange = new Range(change.oldTailScreenPosition, change.oldHeadScreenPosition)
newRange = new Range(change.newTailScreenPosition, change.newHeadScreenPosition)
if oldRange.intersectsRowRange(startRow, endRow - 1)
@removeFromLineDecorationCaches(decoration, oldRange)
intersectsVisibleRowRange = true
if newRange.intersectsRowRange(startRow, endRow - 1)
@addToLineDecorationCaches(decoration, newRange)
intersectsVisibleRowRange = true
if intersectsVisibleRowRange
@updateLinesState() if decoration.isType('line')
@updateLineNumbersState() if decoration.isType('line-number')
if decoration.isType('highlight')
@updateHighlightState(decoration)
if decoration.isType('overlay')
@updateOverlaysState()
didDestroyDecoration: (decoration) ->
if decoration.isType('line') or decoration.isType('line-number')
@removeFromLineDecorationCaches(decoration, decoration.getMarker().getScreenRange())
@updateLinesState() if decoration.isType('line')
@updateLineNumbersState() if decoration.isType('line-number')
if decoration.isType('highlight')
@updateHighlightState(decoration)
if decoration.isType('overlay')
@updateOverlaysState()
highlightDidFlash: (decoration) ->
flash = decoration.consumeNextFlash()
if decorationState = @state.content.highlights[decoration.id]
decorationState.flashCount++
decorationState.flashClass = flash.class
decorationState.flashDuration = flash.duration
@emitter.emit "did-update-state"
didAddDecoration: (decoration) ->
@observeDecoration(decoration)
if decoration.isType('line') or decoration.isType('line-number')
@addToLineDecorationCaches(decoration, decoration.getMarker().getScreenRange())
@updateLinesState() if decoration.isType('line')
@updateLineNumbersState() if decoration.isType('line-number')
else if decoration.isType('highlight')
@updateHighlightState(decoration)
else if decoration.isType('overlay')
@updateOverlaysState()
updateDecorations: ->
@lineDecorationsByScreenRow = {}
@lineNumberDecorationsByScreenRow = {}
@highlightDecorationsById = {}
visibleHighlights = {}
startRow = @computeStartRow()
endRow = @computeEndRow()
return unless 0 <= startRow <= endRow <= Infinity
for markerId, decorations of @model.decorationsForScreenRowRange(startRow, endRow - 1)
range = @model.getMarker(markerId).getScreenRange()
for decoration in decorations
if decoration.isType('line') or decoration.isType('line-number')
@addToLineDecorationCaches(decoration, range)
else if decoration.isType('highlight')
visibleHighlights[decoration.id] = @updateHighlightState(decoration)
for id of @state.content.highlights
unless visibleHighlights[id]
delete @state.content.highlights[id]
@emitter.emit 'did-update-state'
removeFromLineDecorationCaches: (decoration, range) ->
for row in [range.start.row..range.end.row] by 1
delete @lineDecorationsByScreenRow[row]?[decoration.id]
delete @lineNumberDecorationsByScreenRow[row]?[decoration.id]
addToLineDecorationCaches: (decoration, range) ->
marker = decoration.getMarker()
properties = decoration.getProperties()
return unless marker.isValid()
if range.isEmpty()
return if properties.onlyNonEmpty
else
return if properties.onlyEmpty
omitLastRow = range.end.column is 0
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
if decoration.isType('line')
@lineDecorationsByScreenRow[row] ?= {}
@lineDecorationsByScreenRow[row][decoration.id] = decoration
if decoration.isType('line-number')
@lineNumberDecorationsByScreenRow[row] ?= {}
@lineNumberDecorationsByScreenRow[row][decoration.id] = decoration
updateHighlightState: (decoration) ->
return unless @hasRequiredMeasurements()
startRow = @computeStartRow()
endRow = @computeEndRow()
properties = decoration.getProperties()
marker = decoration.getMarker()
range = marker.getScreenRange()
if decoration.isDestroyed() or not marker.isValid() or range.isEmpty() or not range.intersectsRowRange(startRow, endRow - 1)
delete @state.content.highlights[decoration.id]
@emitter.emit 'did-update-state'
return
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
if range.isEmpty()
delete @state.content.highlights[decoration.id]
@emitter.emit 'did-update-state'
return
highlightState = @state.content.highlights[decoration.id] ?= {
flashCount: 0
flashDuration: null
flashClass: null
}
highlightState.class = properties.class
highlightState.deprecatedRegionClass = properties.deprecatedRegionClass
highlightState.regions = @buildHighlightRegions(range)
@emitter.emit 'did-update-state'
true
observeCursor: (cursor) ->
didChangePositionDisposable = cursor.onDidChangePosition =>
@pauseCursorBlinking()
@updateCursorsState()
didChangeVisibilityDisposable = cursor.onDidChangeVisibility(@updateCursorsState.bind(this))
didDestroyDisposable = cursor.onDidDestroy =>
@disposables.remove(didChangePositionDisposable)
@disposables.remove(didChangeVisibilityDisposable)
@disposables.remove(didDestroyDisposable)
@updateCursorsState()
@disposables.add(didChangePositionDisposable)
@disposables.add(didChangeVisibilityDisposable)
@disposables.add(didDestroyDisposable)
didAddCursor: (cursor) ->
@observeCursor(cursor)
@pauseCursorBlinking()
@updateCursorsState()
startBlinkingCursors: ->
@toggleCursorBlinkHandle = setInterval(@toggleCursorBlink.bind(this), @getCursorBlinkPeriod() / 2)
stopBlinkingCursors: ->
clearInterval(@toggleCursorBlinkHandle)
toggleCursorBlink: ->
@state.content.blinkCursorsOff = not @state.content.blinkCursorsOff
@emitter.emit 'did-update-state'
pauseCursorBlinking: ->
@state.content.blinkCursorsOff = false
@stopBlinkingCursors()
@startBlinkingCursorsAfterDelay ?= _.debounce(@startBlinkingCursors, @getCursorBlinkResumeDelay())
@startBlinkingCursorsAfterDelay()
@emitter.emit 'did-update-state'
+1 -1
Ver Arquivo
@@ -283,7 +283,7 @@ class TextEditorView extends View
setShowIndentGuide: (showIndentGuide) ->
deprecate 'This is going away. Use atom.config.set("editor.showIndentGuide", true|false) instead'
atom.config.set("editor.showIndentGuide", showIndentGuide)
@component.setShowIndentGuide(showIndentGuide)
setSoftWrap: (softWrapped) ->
deprecate 'Use TextEditor::setSoftWrapped instead. You can get the editor via editorView.getModel()'
+2 -11
Ver Arquivo
@@ -717,13 +717,9 @@ class TextEditor extends Model
# {Delegates to: DisplayBuffer.bufferRowsForScreenRows}
bufferRowsForScreenRows: (startRow, endRow) -> @displayBuffer.bufferRowsForScreenRows(startRow, endRow)
screenRowForBufferRow: (row) -> @displayBuffer.screenRowForBufferRow(row)
# {Delegates to: DisplayBuffer.getMaxLineLength}
getMaxScreenLineLength: -> @displayBuffer.getMaxLineLength()
getLongestScreenRow: -> @displayBuffer.getLongestScreenRow()
# Returns the range for the given buffer row.
#
# * `row` A row {Number}.
@@ -1353,19 +1349,14 @@ class TextEditor extends Model
getLineDecorations: (propertyFilter) ->
@displayBuffer.getLineDecorations(propertyFilter)
# Soft-deprecated (forgot to deprecated this pre 1.0)
getGutterDecorations: (propertyFilter) ->
deprecate("Use ::getLineNumberDecorations instead")
@getLineNumberDecorations(propertyFilter)
# Extended: Get all decorations of type 'line-number'.
#
# * `propertyFilter` (optional) An {Object} containing key value pairs that
# the returned decorations' properties must match.
#
# Returns an {Array} of {Decoration}s.
getLineNumberDecorations: (propertyFilter) ->
@displayBuffer.getLineNumberDecorations(propertyFilter)
getGutterDecorations: (propertyFilter) ->
@displayBuffer.getGutterDecorations(propertyFilter)
# Extended: Get all decorations of type 'highlight'.
#