Comparar commits

..

251 Commits

Autor SHA1 Mensagem Data
Kevin Sawicki 443056ef5c Mention faster startup time 2013-04-24 22:10:57 -07:00
Kevin Sawicki 6b0a466596 Mention newly included bundles 2013-04-24 22:00:01 -07:00
Kevin Sawicki d6eb1aba75 Mention hyperlink opening 2013-04-24 21:51:20 -07:00
Kevin Sawicki c1769c3de1 Get this changelog started 2013-04-24 21:48:59 -07:00
Kevin Sawicki d37605766d Remove testing block 2013-04-24 18:08:27 -07:00
Kevin Sawicki 763c59764f Include css grammar in fenced block 2013-04-24 18:06:27 -07:00
Kevin Sawicki ec54330096 Add language name to fenced scopes 2013-04-24 18:03:35 -07:00
Kevin Sawicki ac225731d5 Remove testing block 2013-04-24 18:01:04 -07:00
Kevin Sawicki 2803da2f8b Replace offsUtilscreen with offscreen 2013-04-24 17:59:50 -07:00
Kevin Sawicki 0640631d07 Include JS/CoffeeScript grammars in markdown code blocks 2013-04-24 17:57:59 -07:00
Kevin Sawicki 4e8c6e29ad Use @ instead of this. 2013-04-24 17:57:59 -07:00
probablycorey 915d2e42b7 Store cefode versions in /tmp/atom-cached-cefodes
This will make rake cleaning go faster
2013-04-24 15:19:11 -07:00
Corey Johnson & Nathan Sobo cd7162b3bc Fix event emitter error
Introduced in 9cc4c2e5de because
of botched merge conflict resolution
2013-04-24 14:17:47 -07:00
Corey Johnson & Nathan Sobo 96e91f5fa1 Use correct case when requiring subscriber 2013-04-24 14:15:09 -07:00
Corey Johnson & Nathan Sobo 24ce415283 Move event listener calls from resetDisplay to edit
Fixes #140
2013-04-24 11:41:56 -07:00
probablycorey 2a6c4b04b1 Lower case custom attribute linenumber.
All attribute names on HTML elements in HTML documents get ASCII-lowercased
automatically, so the restriction on ASCII uppercase letters doesn't affect such
documents. Via http://www.w3.org/html/wg/drafts/html/master/single-page.html
2013-04-24 11:41:56 -07:00
probablycorey 9cc4c2e5de Allow off to be called before on without error
Previously calling `something.off 'event-name', handler` would throw
an error unless `on` was called first.
2013-04-24 11:41:56 -07:00
Kevin Sawicki 7e9c39fd55 Remove less hack
less is no longer vendored and so the previous
window hack is no longer needed.
2013-04-24 11:36:18 -07:00
Kevin Sawicki 09967db742 Don't count DOM elements for operations count
The number of DOM elements rendered is fluid and using this
number displays inaccurate results as new operations are rendered.

Instead just set the operation count when creating the PathView
based on considering all the operations available.

Closes #502
2013-04-24 10:32:55 -07:00
Kevin Sawicki 946f908ab7 Use relative require paths 2013-04-24 10:12:30 -07:00
Kevin Sawicki 8fb61168b0 doc: mention cson and less files 2013-04-24 10:12:30 -07:00
Kevin Sawicki b65e1485a7 Copy folders to ~/.atom directory asynchronously 2013-04-24 10:12:30 -07:00
Kevin Sawicki 1c8509b873 Look for .json extension first 2013-04-24 10:12:30 -07:00
Kevin Sawicki 044396d41a Remove unneeded empty array fallback 2013-04-24 10:12:30 -07:00
Kevin Sawicki 41229f50f7 Look for .json extension first 2013-04-24 10:12:29 -07:00
Kevin Sawicki 1368026db2 Remove measure calls 2013-04-24 10:12:29 -07:00
Kevin Sawicki 36e725d5d5 Precompile theme metadata to JSON 2013-04-24 10:12:29 -07:00
Kevin Sawicki fc00688454 Use fsUtils.readObject() to read theme metadata 2013-04-24 10:12:29 -07:00
Kevin Sawicki df9c30d174 Precompile .less theme files 2013-04-24 10:12:29 -07:00
Kevin Sawicki 1b6bd1f32f Only add view to attached editors 2013-04-24 10:12:29 -07:00
Kevin Sawicki 2f5a99fac2 Install atom command asynchronously 2013-04-24 10:12:29 -07:00
Kevin Sawicki b27b0c7d4f Defer require and only add view to pane-based editors 2013-04-24 10:12:29 -07:00
Kevin Sawicki d92db4a7a8 💄 2013-04-24 10:12:29 -07:00
Kevin Sawicki 7ef87cb6c4 Load scoped properties asynchronously 2013-04-24 10:12:29 -07:00
Kevin Sawicki 001fef384c Remove extra initialization of scoped properties 2013-04-24 10:12:29 -07:00
Kevin Sawicki ab2a298994 💄 2013-04-24 10:12:28 -07:00
Kevin Sawicki bef3c50fe5 Load scoped properties after grammars load 2013-04-24 10:12:28 -07:00
Nathan Sobo 4a5a155511 Log non-zero exits and stderr output from LoadPathsTask subprocess
This will help us investigate issue #514
2013-04-24 10:26:15 -06:00
Garen Torikian 3087fabc21 Merge pull request #511 from github/no-trim-whitespace
Leave Markdown files alone when trimming whitespace
2013-04-23 17:22:37 -07:00
Garen Torikian 41c79789e3 Make words more consistent 2013-04-23 17:15:20 -07:00
Garen Torikian 0ae599ad6f double space outta my face 2013-04-23 17:15:20 -07:00
Garen Torikian 1b2b8861ee fix specs 2013-04-23 17:15:20 -07:00
Garen Torikian f85e1ccf9a modify whitespace replace fn directly 2013-04-23 17:15:19 -07:00
Garen Torikian 4ed36ec095 Fix dem specs 2013-04-23 17:15:19 -07:00
Garen Torikian 4fc7e1a9d5 Leave Markdown files alone 2013-04-23 17:15:19 -07:00
Garen Torikian 7940820877 Update display-buffer.coffee 2013-04-23 15:41:02 -07:00
Garen Torikian e4eea237b5 Update display-buffer.coffee 2013-04-23 15:37:14 -07:00
Nathan Sobo 1d6c2cdf27 In documentation strings ,s/Integer/Number/g 2013-04-23 16:30:23 -06:00
Nathan Sobo c51db80d6e Some more docs fixes
/cc @gjtorikian
2013-04-23 16:17:36 -06:00
Nathan Sobo 38c2509927 Fix some documentation 2013-04-23 16:11:15 -06:00
Corey Johnson & Nathan Sobo caed3d39de Revert "Don't tokenize files that have more than 10000 lines"
This was a premature optimization. The real problem is
with the spell-check package.

This reverts commit 987d1da233.
2013-04-23 13:50:02 -07:00
Corey Johnson & Nathan Sobo 693a495618 Use _.spliceWithArray in tokenized buffer 2013-04-23 12:01:16 -07:00
Corey Johnson & Nathan Sobo 987d1da233 Don't tokenize files that have more than 10000 lines 2013-04-23 12:00:57 -07:00
Corey Johnson & Nathan Sobo 840efa3d82 Use _.spliceWithArray when splicing in new lines 2013-04-23 11:19:54 -07:00
Corey Johnson & Nathan Sobo 9f235103f8 Add _.spliceWithArray to avoid stack overflows when splicing huge arrays 2013-04-23 11:16:55 -07:00
Kevin Sawicki 1ccf026a83 Set/get text using edit session instead of buffer 2013-04-23 08:54:43 -07:00
Kevin Sawicki 2d897ba415 Remove trailing whitespace 2013-04-23 08:54:18 -07:00
Kevin Sawicki c3045dd230 doc: tweak eventNames param description 2013-04-23 08:42:33 -07:00
Kevin Sawicki & Nathan Sobo e50aa56d02 Space separate event names 2013-04-23 08:39:26 -07:00
Kevin Sawicki & Nathan Sobo 228fa1abe5 Store injections grammars on Syntax global 2013-04-23 08:39:26 -07:00
Kevin Sawicki & Nathan Sobo 125c41a2e2 Support space-separated event names in EventEmitter.on()/off() 2013-04-23 08:39:26 -07:00
Kevin Sawicki & Nathan Sobo b9ad462c69 Retokenize when grammar with matching injection selector is updated 2013-04-23 08:39:26 -07:00
Kevin Sawicki & Nathan Sobo 0d35943386 Retokenize when grammar with matching injection selector is loaded 2013-04-23 08:39:26 -07:00
Kevin Sawicki & Nathan Sobo b1527a7982 Add getter and setter for buffer text 2013-04-23 08:39:26 -07:00
Kevin Sawicki eec6518278 Add package to open links on ctrl-O 2013-04-23 08:39:26 -07:00
Kevin Sawicki a447ab4edc Add link segment to GFM underline scope 2013-04-23 08:39:26 -07:00
Kevin Sawicki f0cf337857 Support grammars with an injectionSelector
These grammars can now contribute rules to other grammars when
their selector matches the current scope stack.
2013-04-23 08:39:26 -07:00
Kevin Sawicki 927e9c3de8 Upgrade to oniguruma 0.11 2013-04-23 08:39:25 -07:00
Kevin Sawicki 4acb3acebc Use substring instead of substr 2013-04-23 08:39:25 -07:00
Kevin Sawicki 63d665e2cb Replace capture groups in pattern names with match 2013-04-23 08:39:25 -07:00
Kevin Sawicki a720919dd8 Add hyperlink helper TextMate bundle 2013-04-23 08:39:25 -07:00
Kevin Sawicki 220b9dadbb Don't show null grammar in grammar selector 2013-04-22 22:13:21 -07:00
Nathan Sobo 608ffd27ad Slightly more correct 2013-04-22 11:34:03 -06:00
Nathan Sobo 35f0b7b49e Fix #505. Update grammars if any included grammars are updated. 2013-04-22 11:34:03 -06:00
Nathan Sobo b856ab16cf Add SQL textmate bundle 2013-04-22 11:34:03 -06:00
Nathan Sobo 9866e84c9f In LanguageMode, unsubscribe from old grammar when switching grammars 2013-04-22 11:34:03 -06:00
Nathan Sobo 444325893b Allow subscribers to unsubscribe on a per-object basis
This makes use of the new ES6 WeakMap feature, which allows for a hash
map that's keyed by object.
2013-04-22 11:34:03 -06:00
Nathan Sobo 93910201b0 Re-tokenize buffer when its grammar is updated
This can happen if a grammar that the grammar includes is added or
removed from the syntax global.
2013-04-22 11:34:03 -06:00
Nathan Sobo 9204836d70 Update grammars when grammars they include are added/removed
If the Ruby on Rails grammar depends on HTML, but it isn't loaded, its
syntax highlighting won't include HTMl tokens. If we later load HTML,
we should update any buffer with the Rails grammar to reflect the
change. This commit changes grammars to memoize their initial rule and
repository. If an included grammar is added or removed, we clear the
memoized rules and emit a 'grammar-updated' event. Any tokenized
buffer that points to this grammar can then retokenize to reflect the
newly available/unavailable included grammar.
2013-04-22 11:33:59 -06:00
Kevin Sawicki e6274b2f45 Update expected require path for changes in 8e2938ea 2013-04-20 09:57:34 -07:00
Kevin Sawicki 8cd7adceb5 Remove ignore of doc/assets
Biscotto upgrade removes the need to ignore this
folder which was previously when generating the
doc stats.
2013-04-20 09:53:50 -07:00
Garen Torikian d52631bab5 Update package.json
Bump for excessive dir creation
2013-04-19 18:28:42 -07:00
Corey Johnson 8e2938ea82 Updae package generator require paths 2013-04-19 14:59:54 -07:00
Kevin Sawicki f8011d9cc9 doc: capitalize number and wrap in {} 2013-04-19 11:05:54 -07:00
Kevin Sawicki 8bf9598f7b doc: pluralize event 2013-04-19 11:03:38 -07:00
Kevin Sawicki 74d6bf776d Alphabetize class exports 2013-04-19 10:58:36 -07:00
Kevin Sawicki 4fa4bc5c17 Remove extra newline 2013-04-19 10:58:36 -07:00
Kevin Sawicki abc025326f 💄 2013-04-19 10:58:36 -07:00
Kevin Sawicki 2d054e715e Mark matchers as internal 2013-04-19 10:58:36 -07:00
Kevin Sawicki 9b621f330b Doc TextMateScopeSelector 2013-04-19 10:58:36 -07:00
Kevin Sawicki f84402c4cf Rename selector ivar to source 2013-04-19 10:58:36 -07:00
Kevin Sawicki 62c94959e1 Move injections spec to TextMateGrammar spec
This spec was previously in the TokenizeBuffer spec which
required having a PHP fixture and setting up an edit session
to verify a line could be tokenized which was unnecessary.
2013-04-19 10:58:35 -07:00
Kevin Sawicki 23fc73733f Rename AsteriskMatcher to TrueMatcher 2013-04-19 10:58:35 -07:00
Kevin Sawicki 6bda6e7644 Update remaining Asterix to Asterisk 2013-04-19 10:58:35 -07:00
Kevin Sawicki & Nathan Sobo faac67c88e Rename asterix to asterisk 2013-04-19 10:58:35 -07:00
Kevin Sawicki & Nathan Sobo 1f4febcfaf Ignore child captures of captures with patterns 2013-04-19 10:58:35 -07:00
Kevin Sawicki & Nathan Sobo b37468871c Use TextMateGrammar.tokenizeLine() to create capture rule tokens 2013-04-19 10:58:35 -07:00
Kevin Sawicki c63834924a Support patterns included in captures
Previously only the capture's name was considered when processing
tokens for capture indices.

Now the capture's patterns are matched against the captured region
if they exist.
2013-04-19 10:58:35 -07:00
Kevin Sawicki 1091875ca1 Index into array instead of removing matchers 2013-04-19 10:58:35 -07:00
Kevin Sawicki 66b80d9682 💄 2013-04-19 10:58:35 -07:00
Kevin Sawicki faca7f091a Include PEG js bundle 2013-04-19 10:58:35 -07:00
Kevin Sawicki 9489ec6787 Add specs for & selectors 2013-04-19 10:58:35 -07:00
Kevin Sawicki d5723aa18d Port scope selector matchers to CoffeeScript 2013-04-19 10:58:35 -07:00
Kevin Sawicki & Nathan Sobo 1dffb9237a Use the earliest result when both injection and non-injection patterns match 2013-04-19 10:58:34 -07:00
Kevin Sawicki & Nathan Sobo 0a92f68aac Remove unused variable 2013-04-19 10:58:34 -07:00
Kevin Sawicki 40939ab984 Remove unused injected variable 2013-04-19 10:58:34 -07:00
Kevin Sawicki da898a5344 Cache scanners used in injections 2013-04-19 10:58:34 -07:00
Kevin Sawicki 88ebad2b7a Add Injections helper class 2013-04-19 10:58:34 -07:00
Kevin Sawicki c9edc3b2d6 Add createScanner() helper to Rule 2013-04-19 10:58:34 -07:00
Kevin Sawicki 1407f6c1f0 Add getRegex() helper to Pattern 2013-04-19 10:58:34 -07:00
Kevin Sawicki 3542e879bc Set injected to false by default 2013-04-19 10:58:34 -07:00
Kevin Sawicki c2eca1ff99 Add initial support for injection grammars
Build scope selectors and patterns when setting up the grammar
for all entries under the grammar's injection object.

Include the injection patterns in the scanner when the injection's
scope selector matches the current rule stack.
2013-04-19 10:58:34 -07:00
Kevin Sawicki 2568aa086e 💄 2013-04-19 10:58:34 -07:00
Kevin Sawicki 7cb8850ed2 Expect single scope as parameter to scope matcher 2013-04-19 10:58:34 -07:00
Kevin Sawicki beeaa01d22 Support space-separated scopes in selector parser 2013-04-19 10:58:33 -07:00
Kevin Sawicki 3e5448b698 Add initial TextMate scope selector parser 2013-04-19 10:58:33 -07:00
Kevin Sawicki ed9f46b39f Upgrade TextMate PHP bundle to current master 2013-04-19 10:58:33 -07:00
Kevin Sawicki 76484acb21 Call atom.show() from a setTimeout()
The window was previously flashing white if atom.show() was called
directly after window.startup()

Wrapping the call in a setTimeout with a zero delay seems to prevent
the white flash and the perceived amount of time to display the contents
appears to be the same.
2013-04-19 10:15:50 -07:00
Kevin Sawicki 36768251f8 Default ranges to empty array when no edit session
Previously an exception would be thrown if a '/' pattern was evaluated
when there was no active edit session.
2013-04-19 10:05:48 -07:00
Kevin Sawicki b4ab10403d Use lower case submodule paths
Lowercase the Go and Mustache bundle paths to be
consistent with all others.
2013-04-18 21:34:31 -07:00
Kevin Sawicki 086c7f7133 Run biscotto directly through coffee
CI had issues finding coffee when exec'ing directly.
2013-04-18 21:08:33 -07:00
Kevin Sawicki 4b48e07f83 Specify cwd instead of cd'ing 2013-04-18 20:41:17 -07:00
Kevin Sawicki ca1177efea Ignore doc/assets 2013-04-18 20:40:48 -07:00
Kevin Sawicki a149357bd5 💄 2013-04-18 20:21:34 -07:00
Kevin Sawicki b5474790cb Remove unused imports 2013-04-18 20:18:37 -07:00
Kevin Sawicki 848761710e 💄 2013-04-18 20:16:49 -07:00
Garen Torikian 53f03d1b30 Missed a spot 2013-04-18 20:10:30 -07:00
Garen Torikian dcbd6f07e5 Merge pull request #498 from github/api/docs
Add API documentation for src/app
2013-04-18 18:58:41 -07:00
Garen Torikian 42290e87f8 80 is good. 2013-04-18 18:51:46 -07:00
Garen Torikian df528f954b Bump biscotto one last time 2013-04-18 18:51:23 -07:00
Garen Torikian 5ee388cede Get it to a proper 80% 2013-04-18 18:50:22 -07:00
Garen Torikian 5d7b4ec04b Bump biscotto again 2013-04-18 15:24:19 -07:00
Garen Torikian cabee75f8a 💄 2013-04-18 15:02:22 -07:00
Garen Torikian 87f991a35d Remove package doc for now 2013-04-18 15:01:52 -07:00
Garen Torikian 14a0010517 Keep updating Internal hiding 2013-04-18 14:58:19 -07:00
Garen Torikian e478d9b7a0 Add more corrections off of the #Internal syntax 2013-04-18 13:38:26 -07:00
Garen Torikian 0acccc383e Remove test for folder not documented 2013-04-18 13:24:55 -07:00
Garen Torikian 3303f5a741 Bump biscotto 2013-04-18 13:05:46 -07:00
Garen Torikian a50cfa26cc Updates for new biscotto 2013-04-18 13:05:40 -07:00
Garen Torikian 4d2b78b1f8 Bump biscotto version 2013-04-18 11:22:39 -07:00
Garen Torikian 9f6ef4beec Merge branch 'master' into api/docs 2013-04-18 11:20:23 -07:00
Kevin Sawicki f93b09fc9e Don't try to active windows without a path
Previously Atom would crash if a path was opened when there
was already a window open that did not have a path, such as
the specs window.

Now window controllers without a path are skipped during the
checks to activate an existing window for an opened path.
2013-04-17 18:45:24 -07:00
probablycorey 599328cb9c Better error message when Atom.app is not found 2013-04-17 16:26:13 -07:00
probablycorey 9b8abe3552 If Atom.app doesn't exist, wait 5 seconds and try again.
Closes #351

When Sparkle updates Atom, it deletes the app and replaces it. This
causes the `atom` cli to fail when called within this brief time
period. This gives `atom` a five second grace period if it is not
found.
2013-04-17 16:25:42 -07:00
Garen Torikian 130baf2235 Merge branch 'master' into api/docs 2013-04-17 16:08:18 -07:00
probablycorey 5d84c8117e Explain the meta key in the getting started docs
Closes #328
2013-04-17 14:30:24 -07:00
Kevin Sawicki 379bf54c40 Don't set widthChanged handler on gutter
The gutter now uses flex box so the width is no longer explicitly
set based on the number of lines.
2013-04-17 09:01:27 -07:00
Kevin Sawicki 88ae70eb19 Remove duplicate property 2013-04-16 17:56:06 -07:00
Kevin Sawicki 1aa0e52cea 💄 2013-04-16 17:52:00 -07:00
Kevin Sawicki 8c138ebd04 Remove unused ivar 2013-04-16 17:47:30 -07:00
Nathan Sobo cada940345 Load full bootstrap 2013-04-16 17:24:21 -06:00
Nathan Sobo bbbdacab74 Avoid the .label class because it conflicts with bootstrap 2013-04-16 17:24:21 -06:00
Kevin Sawicki aa27b3d464 Set min-height on tree to 100% 2013-04-16 15:30:33 -07:00
Kevin Sawicki & Nathan Sobo 807c6878c1 Set width of hidden input to 1 pixel
Previously the width was set to the width of a single character which
caused issues if the char width was currently zero and since the hidden
input no longer has padding or border the width of the input would end
up being zero which would prevent it from gaining focus.
2013-04-16 15:20:16 -07:00
Nathan Sobo 45ede6bc79 💄 rename treeViewList outlet to list 2013-04-16 15:26:31 -06:00
Corey Johnson 3c95fbd72c Remove redundant conditional 2013-04-16 14:23:42 -07:00
Corey Johnson & Nathan Sobo ca49d0714c Restore previous selections after tailing newlines are added
Closes #496
2013-04-16 14:23:42 -07:00
Corey Johnson & Nathan Sobo f21571eab1 Rename isFirstPoint to isHead in marker.updatePosition 2013-04-16 14:23:42 -07:00
Kevin Sawicki bc99e72b3d Verify the buttons individually in the spec
The outer expand-collapse element is no longer in the view.
2013-04-16 13:56:27 -07:00
Kevin Sawicki & Nathan Sobo fe5d73c02a Use bootstrap buttons in command panel
Thanks @mdo!
2013-04-16 12:28:35 -07:00
Kevin Sawicki & Nathan Sobo a1cab1a692 Log instead of re-throwing less parser errors 2013-04-16 12:10:07 -07:00
Nathan Sobo afa58160b3 Oops. Fix tree-view auto-scrolling and specs after breaking them. 2013-04-16 11:59:25 -06:00
Nathan Sobo 84bc5d1e56 Fix syntax error in select-list.less of atom-dark-ui theme 2013-04-16 11:53:48 -06:00
Nathan Sobo 917ab3fe7b Ensure tree view highlights extend full-width, even when scrolled
The .tree-view-wrapper element is renamed to `.tree-view-resizer` to
clarify its purpose, and the actual draggable div is renamed to
`.tree-view-resize-handle`. Then a new div is introduced beneath the
resized wrapper called `tree-view-scroller`. This element has 100%
width/height and overflow scroll, allowing the actual tree view list
to *not* scroll. It uses the cutting edge `min-content` property as
its min width, which ensures it's always wide enough to contain its 
content even when the scroller wrapper is narrower. This allows the
absolutely-positioned highlights to always extend *at least* across
the full width of the list elements.
2013-04-16 11:20:56 -06:00
probablycorey a8f27a7848 Convert select list to Less style markup 2013-04-16 10:20:00 -07:00
Kevin Sawicki 58bf33fc6f Remove margins on nested matches list 2013-04-16 10:08:15 -07:00
Nathan Sobo 6107734460 Fix command panel preview list header styling 2013-04-16 10:57:50 -06:00
Nathan Sobo 4c037d53e2 Eliminate :focus outline 2013-04-16 10:57:50 -06:00
Kevin Sawicki 44871c84cf Ignore .git folder 2013-04-16 09:46:49 -07:00
Nathan Sobo 5071f083a1 Revert "Load all of bootstrap"
This reverts commit e59ab14ad3.
2013-04-16 10:38:35 -06:00
Kevin Sawicki a2035bc305 Move error-messages class under command-panel class 2013-04-16 09:33:45 -07:00
Kevin Sawicki 27252961cf Remove list style type on matches class 2013-04-16 09:29:31 -07:00
Kevin Sawicki 0102c0fd8d Bind click events using new button outlets 2013-04-16 09:26:24 -07:00
Kevin Sawicki f190dab5e6 Remove classes from buttons
The collapse class was causing the button to be very small since it
is defined in bootstrap with height 0.
2013-04-16 09:22:18 -07:00
Kevin Sawicki e037bf1db9 Remove list type and margin from error messages 2013-04-16 09:18:43 -07:00
Kevin Sawicki 865b70e16d Don't exclude node_modules/less
Less is no longer vendored and so it should now be
copied over.
2013-04-16 08:46:13 -07:00
Nathan Sobo e59ab14ad3 Load all of bootstrap 2013-04-15 20:45:56 -06:00
Nathan Sobo 1ce9cdff0c Make precompilation of less stylesheets work with @import directives 2013-04-15 20:45:56 -06:00
Nathan Sobo 140d5d5a85 💄 Use outlet 2013-04-15 20:45:56 -06:00
Nathan Sobo 8c648e9226 Fix tree-view specs 2013-04-15 20:45:56 -06:00
Nathan Sobo cd9f6d4c62 Remove tree-view margin bottom 2013-04-15 20:45:56 -06:00
Nathan Sobo 305a1b91b8 Set box-sizing to content-box for autocomplete 2013-04-15 20:45:56 -06:00
Nathan Sobo fff4531663 Fix select-list auto-scrolling specs 2013-04-15 20:45:55 -06:00
Nathan Sobo 9d987ac911 Set box-sizing: content-box on image-view img tags
Bootstrap sets box-sizing: border-box everywhere. It's often a good
idea, but in this case it screws up the specs for the image view.
2013-04-15 20:45:55 -06:00
Nathan Sobo bc03810589 Set height on image view, not parent
Setting the height of the parent caused #jasmine-content to be tall
for the remainder of the specs
2013-04-15 20:45:55 -06:00
Corey Johnson & Nathan Sobo d95016307a Fix mini-editor appearance by clearing styles on hidden input. 2013-04-15 20:45:55 -06:00
Corey Johnson & Nathan Sobo 8f0bffc589 Don't set height on mini editor explicitly. Let 'height: auto' do it.
We're not sure why this is here, and can't see a problem when we remove
it.
2013-04-15 20:45:55 -06:00
Nathan Sobo d82daeccee Fix jasmine styling 2013-04-15 20:45:55 -06:00
Nathan Sobo 23f66c12a4 Make modified tab's close icon remain blue on hover 2013-04-15 20:45:55 -06:00
Nathan Sobo 39f920165d Report errors with no stack when loading packages 2013-04-15 20:45:55 -06:00
Nathan Sobo e0c8c5ccb7 Clean up tabs styling using LESS features 2013-04-15 20:45:55 -06:00
Nathan Sobo c1e4d2f1f1 Make tree-view CSS bootstrap-compatible. Use less and octicon-mixins. 2013-04-15 20:45:55 -06:00
Nathan Sobo 312f04f44d Add octicon-mixins.less
This file contains mixins that make it easy to turn any selector into
an icon. You use them in the following way:

```less
@import "octicon-mixins.less";

.entry .disclosure-arrow {
  .mini-icon(arr-collapsed);
}

.entry.expanded .disclosure-arrow {
  .mini-icon(arr-expanded);
}
```

There is also the `.mega-icon` mixin, and you are free to pass a size
as a second parameter, like this: `.mega-icon(octocat, 64px)`
2013-04-15 20:45:55 -06:00
Nathan Sobo e871929aae Load a subset of bootstrap from atom.less and nuke reset.less 2013-04-15 20:45:55 -06:00
Nathan Sobo 91cbcf0073 Update editor lines & cursors to not use bootstrap-styled pre element 2013-04-15 20:45:55 -06:00
Nathan Sobo 738fc31f56 Add twitter bootstrap submodule and put it on less search path 2013-04-15 20:45:54 -06:00
Nathan Sobo bbf7c6ca1b Replace requireStylesheet calls w/ @import directives in atom.less 2013-04-15 20:45:54 -06:00
Nathan Sobo 2f008aa886 Use less NPM module instead of vendored less
We're currently using nathansobo's fork, which doesn't depend on
`window` being undefined to operate in Node mode.
2013-04-15 20:45:54 -06:00
Garen Torikian 4c56e8bb57 Merge branch 'master' into api/docs 2013-04-14 09:43:06 -07:00
Garen Torikian c4e713f121 Update biscotto 2013-04-14 01:44:59 -07:00
Garen Torikian 99333d4444 Don't commit output 2013-04-12 22:37:07 -05:00
Garen Torikian 8bd02358d2 Should I regret a lack of better commit messages? 2013-04-12 22:35:54 -05:00
Cheng Zhao 558e41f740 Make main window restart renderer process when reloaded for 4 times.
Fix #495.
2013-04-13 10:24:58 +08:00
Garen Torikian 37b3784129 Finish Selections, add Fold 2013-04-12 21:10:57 -05:00
Garen Torikian f3b7874007 Get a good chunk of Selection} 2013-04-12 15:54:56 -05:00
Garen Torikian 286e70cc57 Hella Editor updates 2013-04-12 15:17:11 -05:00
Garen Torikian b2637aae57 Don't add stats 2013-04-12 14:38:59 -05:00
Garen Torikian 0c5e38680d Internalize a bunch of methods 2013-04-12 14:37:21 -05:00
Garen Torikian e412eb16af Add a proper output dir 2013-04-12 14:36:59 -05:00
Garen Torikian 6963503d6f Finish config 2013-04-12 14:23:43 -05:00
Kevin Sawicki 7b42e975fb Include hidden files when running nak 2013-04-12 11:48:44 -07:00
Kevin Sawicki 26638980bf Upgrade to git-utils 0.14 2013-04-12 11:09:58 -07:00
Kevin Sawicki 9a3821b97e Quit message loop when last browser is closed
Previously CefShutdown() was called after closing the windows
which would prevent them from running their beforeunload callbacks
and saving state properly when cmd-Q the application.

Now the number of open browsers is tracked and the message loop
is quit and the windows are autoreleased only after the browser
is ready to be closed.

Closes #493
2013-04-12 10:22:18 -07:00
Garen Torikian 7e5b6bf5c4 Massive updates 2013-04-11 21:11:12 -05:00
Kevin Sawicki 235cd1fbff Add initial .nakignore file 2013-04-11 15:32:45 -07:00
Kevin Sawicki 224e01102c Use path.join() of fsUtils.join() 2013-04-11 14:37:25 -07:00
Kevin Sawicki 75da75158a Default width to min-width property 2013-04-11 14:26:29 -07:00
Kevin Sawicki ee7a90184b Make autocomplete wide enough to not scroll 2013-04-11 14:18:22 -07:00
Garen Torikian bb8f5bb40c Fix errors 2013-04-11 02:29:48 -05:00
Garen Torikian e0e4936756 Update spec 2013-04-11 02:14:24 -05:00
Garen Torikian 8bcb01383c Add Project 2013-04-10 18:16:54 -05:00
Garen Torikian bd00c5d53d stash 2013-04-10 18:04:14 -05:00
Garen Torikian 1142ae89aa Update spec 2013-04-10 18:02:13 -05:00
Garen Torikian c5557500a9 Add more to specs 2013-04-10 18:01:54 -05:00
Garen Torikian 1963cb66fd Merge branch 'master' into api/docs 2013-04-10 17:23:05 -05:00
Garen Torikian 9432f9703e Merge master 2013-04-10 15:05:21 -05:00
Garen Torikian ec3a137a1a Doc events 2013-04-10 02:27:42 -05:00
Garen Torikian 742a28ca9c Define file stuff 2013-04-10 02:20:55 -05:00
Garen Torikian f677e6633c Fix doc tasks 2013-04-10 02:20:45 -05:00
Garen Torikian 79562f51fd Some more low hanging fruit 2013-04-10 01:28:50 -05:00
Garen Torikian fad987c0f3 More updates--25% 2013-04-09 18:03:39 -05:00
Garen Torikian 756bb5604f Add API doc spec 2013-04-09 18:03:29 -05:00
Garen Torikian abbda0643a Specify more namespaces 2013-04-09 18:02:24 -05:00
Garen Torikian cf9185b512 Merge branch 'master' into api/docs 2013-04-09 16:21:14 -05:00
Garen Torikian dbd924cfb0 Stash 2013-04-09 16:20:49 -05:00
Garen Torikian bf76083939 Merge master 2013-04-09 14:54:17 -05:00
Garen Torikian 49e42c0510 Updates 2013-04-09 14:49:58 -05:00
Garen Torikian 5dc1ddd5b5 Remove invalid biscotto for now 2013-04-09 14:49:51 -05:00
Garen Torikian 19c347903a Add stats tasks 2013-04-09 14:49:41 -05:00
Garen Torikian a16428c5b6 Merge master 2013-04-09 01:37:46 -05:00
Garen Torikian d042fadab1 More updates 2013-04-09 01:18:12 -05:00
Garen Torikian f2698bc6a9 More updates 2013-04-08 02:14:45 -05:00
Garen Torikian fe0dc8181a More doc updates--now at 15% 2013-04-05 14:33:58 -07:00
Garen Torikian 003813f39d Add statsOnly task 2013-04-05 13:54:56 -07:00
Garen Torikian 9e38537239 Hella docs, we're up to 10% coverage 2013-04-04 18:54:36 -07:00
Garen Torikian 3d076daf54 More updates 2013-04-02 17:54:34 -07:00
Garen Torikian da59785c76 Dummy package placeholder 2013-04-02 17:30:25 -07:00
Garen Torikian bd1b407a4b Add that rake task 2013-04-02 17:30:11 -07:00
Garen Torikian 83c6975d34 Add a rake task, of course 2013-04-02 16:47:30 -07:00
Garen Torikian 0b6c817f0a First drop of comments 2013-04-02 14:00:56 -07:00
Garen Torikian 6066030059 Test it out 2013-04-01 09:49:53 -07:00
155 arquivos alterados com 5669 adições e 6281 exclusões
+1
Ver Arquivo
@@ -12,3 +12,4 @@ tags
/cef/
/sources.gypi
/node/
docs/api
+16 -4
Ver Arquivo
@@ -70,9 +70,21 @@
[submodule "vendor/packages/less.tmbundle"]
path = vendor/packages/less.tmbundle
url = https://github.com/mathewbyrne/less.tmbundle.git
[submodule "vendor/packages/Mustache.tmbundle"]
path = vendor/packages/Mustache.tmbundle
[submodule "vendor/packages/mustache.tmbundle"]
path = vendor/packages/mustache.tmbundle
url = https://github.com/kevinsawicki/Mustache.tmbundle.git
[submodule "vendor/packages/Go.tmbundle"]
path = vendor/packages/Go.tmbundle
[submodule "vendor/packages/go.tmbundle"]
path = vendor/packages/go.tmbundle
url = https://github.com/rsms/Go.tmbundle
[submodule "vendor/bootstrap"]
path = vendor/bootstrap
url = https://github.com/twitter/bootstrap
[submodule "vendor/packages/pegjs.tmbundle"]
path = vendor/packages/pegjs.tmbundle
url = https://github.com/alexstrat/PEGjs.tmbundle
[submodule "vendor/packages/sql.tmbundle"]
path = vendor/packages/sql.tmbundle
url = https://github.com/textmate/sql.tmbundle
[submodule "vendor/packages/hyperlink-helper.tmbundle"]
path = vendor/packages/hyperlink-helper.tmbundle
url = https://github.com/textmate/hyperlink-helper.tmbundle
+4
Ver Arquivo
@@ -0,0 +1,4 @@
tags
node_modules
vendor/packages
.git
+5
Ver Arquivo
@@ -0,0 +1,5 @@
* Improved: Startup time
* Added: SQL bundle now included
* Added: PEG.js bundle now included
* Added: Hyperlinks can now be opened with ctrl-O
* Fixed: PHP syntax highlighting
+19
Ver Arquivo
@@ -116,6 +116,25 @@ task :tags do
system %{find src native cef vendor -not -name "*spec.coffee" -type f -print0 | xargs -0 ctags}
end
namespace :docs do
namespace :app do
desc "Builds the API docs in src/app"
task :build do
system %{./node_modules/coffee-script/bin/coffee ./node_modules/biscotto/bin/biscotto -- -o docs/api src/app/}
end
desc "Lists the stats for API doc coverage in src/app"
task :stats do
system %{./node_modules/coffee-script/bin/coffee ./node_modules/biscotto/bin/biscotto -- --statsOnly src/app/}
end
desc "Show which docs are missing"
task :missing do
system %{./node_modules/coffee-script/bin/coffee ./node_modules/biscotto/bin/biscotto -- --listMissing src/app/}
end
end
end
def application_path
applications = FileList["#{BUILD_DIR}/**/Atom.app"]
if applications.size == 0
+10 -1
Ver Arquivo
@@ -1,5 +1,14 @@
#!/bin/sh
open -a /Applications/Atom.app -n --args --executed-from="$(pwd)" --pid=$$ $@
ATOM_PATH=/Applications/Atom.app
if [ ! -d $ATOM_PATH ]; then sleep 5; fi # Wait for Atom to reappear, Sparkle may be replacing it.
if [ ! -d $ATOM_PATH ]; then
echo "Atom Application not found at '$ATOM_PATH'" >&2
exit 1
fi
open -a $ATOM_PATH -n --args --executed-from="$(pwd)" --pid=$$ $@
# Used to exit process when atom is used as $EDITOR
on_die() {
+7 -6
Ver Arquivo
@@ -6,12 +6,13 @@ about configuring, theming, and extending Atom.
## The Command Palette
If there's one key-command you learn in Atom, it should be `meta-p`. You can
always hit `meta-p` to bring up a list of commands that are relevant to the
currently focused UI element. If there is a key binding for a given command, it
is also displayed. This is a great way to explore the system and get to know the
key commands interactively. If you'd like to add or change a binding for a
command, refer to the [key bindings](#customizing-key-bindings) section to learn how.
If there's one key-command you learn in Atom, it should be `meta-p` (`meta` is
synonymous with the ⌘ key). You can always hit `meta-p` to bring up a list of
commands that are relevant to the currently focused UI element. If there is a
key binding for a given command, it is also displayed. This is a great way to
explore the system and get to know the key commands interactively. If you'd like
to add or change a binding for a command, refer to the [key
bindings](#customizing-key-bindings) section to learn how.
![Command Palette](http://f.cl.ly/items/32041o3w471F3C0F0V2O/Screen%20Shot%202013-02-13%20at%207.27.41%20PM.png)
+8 -3
Ver Arquivo
@@ -129,7 +129,7 @@
CefString(&settings.cache_path) = [[self supportDirectory] UTF8String];
CefString(&settings.user_agent) = [userAgent UTF8String];
CefString(&settings.log_file) = "";
CefString(&settings.javascript_flags) = "";
CefString(&settings.javascript_flags) = "--harmony_collections";
settings.remote_debugging_port = 9090;
settings.log_severity = LOGSEVERITY_ERROR;
return settings;
@@ -150,6 +150,9 @@
for (NSWindow *window in [self windows]) {
if (![window isExcludedFromWindowsMenu]) {
AtomWindowController *controller = [window windowController];
if (!controller.pathToOpen) {
continue;
}
if (!openingDirectory) {
BOOL openedPathIsDirectory = false;
[[NSFileManager defaultManager] fileExistsAtPath:controller.pathToOpen isDirectory:&openedPathIsDirectory];
@@ -265,11 +268,13 @@
}
}
- (void)applicationWillTerminate:(NSNotification *)notification {
- (NSApplicationTerminateReply)applicationShouldTerminate:
(NSApplication *)sender {
for (NSWindow *window in [self windows]) {
[window performClose:self];
}
CefShutdown();
return NSTerminateCancel;
}
# pragma mark CefAppProtocol
+10 -2
Ver Arquivo
@@ -1,6 +1,7 @@
#include <sstream>
#include <iostream>
#include <assert.h>
#include "include/cef_app.h"
#include "include/cef_path_util.h"
#include "include/cef_process_util.h"
#include "include/cef_task.h"
@@ -14,7 +15,9 @@
#define REQUIRE_IO_THREAD() assert(CefCurrentlyOn(TID_IO));
#define REQUIRE_FILE_THREAD() assert(CefCurrentlyOn(TID_FILE));
AtomCefClient::AtomCefClient(){
static int numberOfOpenBrowsers = 0;
AtomCefClient::AtomCefClient() {
}
AtomCefClient::AtomCefClient(bool handlePasteboardCommands, bool ignoreTitleChanges) {
@@ -167,17 +170,22 @@ bool AtomCefClient::OnKeyEvent(CefRefPtr<CefBrowser> browser,
void AtomCefClient::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
// REQUIRE_UI_THREAD(); // When uncommented this fails when app is terminated
m_Browser = NULL;
numberOfOpenBrowsers--;
if (numberOfOpenBrowsers == 0) {
CefQuitMessageLoop();
}
}
void AtomCefClient::OnAfterCreated(CefRefPtr<CefBrowser> browser) {
REQUIRE_UI_THREAD();
AutoLock lock_scope(this);
if (!m_Browser.get()) {
if (!m_Browser.get()) {
m_Browser = browser;
}
GetBrowser()->GetHost()->SetFocus(true);
numberOfOpenBrowsers++;
}
void AtomCefClient::OnLoadError(CefRefPtr<CefBrowser> browser,
+3
Ver Arquivo
@@ -86,6 +86,7 @@ class AtomCefClient : public CefClient,
// CefLifeSpanHandler methods
virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser) OVERRIDE;
virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) OVERRIDE;
virtual bool DoClose(CefRefPtr<CefBrowser> browser) OVERRIDE;
// CefLoadHandler methods
@@ -100,11 +101,13 @@ class AtomCefClient : public CefClient,
bool Save(const std::string& path, const std::string& data);
void RestartRendererProcess(CefRefPtr<CefBrowser> browser);
bool IsClosed() { return m_IsClosed; };
protected:
CefRefPtr<CefBrowser> m_Browser;
bool m_HandlePasteboardCommands = false;
bool m_IgnoreTitleChanges = false;
bool m_IsClosed = false;
void FocusNextWindow();
void FocusPreviousWindow();
+8 -1
Ver Arquivo
@@ -156,7 +156,7 @@ void AtomCefClient::Exit(int status) {
}
void AtomCefClient::Log(const char *message) {
std::cout << message << "\n";
NSLog(@"%s", message);
}
void AtomCefClient::GetVersion(int replyId, CefRefPtr<CefBrowser> browser) {
@@ -169,3 +169,10 @@ void AtomCefClient::GetVersion(int replyId, CefRefPtr<CefBrowser> browser) {
replyArguments->SetList(0, CreateReplyDescriptor(replyId, 0));
browser->SendProcessMessage(PID_RENDERER, replyMessage);
}
bool AtomCefClient::DoClose(CefRefPtr<CefBrowser> browser) {
m_IsClosed = true;
NSWindow *window = [browser->GetHost()->GetWindowHandle() window];
[window performClose:window];
return false;
}
+1
Ver Arquivo
@@ -32,6 +32,7 @@ int AtomMain(int argc, char* argv[]) {
[mainNib instantiateWithOwner:application topLevelObjects:nil];
CefRunMessageLoop();
CefShutdown();
}
return 0;
+29 -23
Ver Arquivo
@@ -177,6 +177,10 @@
[self addBrowserToView:self.webView url:[urlString UTF8String] cefHandler:_cefClient];
}
- (BOOL)isBrowserUsable {
return _cefClient && !_cefClient->IsClosed() && _cefClient->GetBrowser();
}
- (void)toggleDevTools {
if (_devToolsView) {
[self hideDevTools];
@@ -188,22 +192,21 @@
- (void)showDevTools {
if (_devToolsView) return;
if (![self isBrowserUsable]) return;
if (_cefClient && _cefClient->GetBrowser()) {
NSRect webViewFrame = _webView.frame;
NSRect devToolsViewFrame = _webView.frame;
devToolsViewFrame.size.height = NSHeight(webViewFrame) / 3;
webViewFrame.size.height = NSHeight(webViewFrame) - NSHeight(devToolsViewFrame);
[_webView setFrame:webViewFrame];
_devToolsView = [[NSView alloc] initWithFrame:devToolsViewFrame];
NSRect webViewFrame = _webView.frame;
NSRect devToolsViewFrame = _webView.frame;
devToolsViewFrame.size.height = NSHeight(webViewFrame) / 3;
webViewFrame.size.height = NSHeight(webViewFrame) - NSHeight(devToolsViewFrame);
[_webView setFrame:webViewFrame];
_devToolsView = [[NSView alloc] initWithFrame:devToolsViewFrame];
[_splitView addSubview:_devToolsView];
[_splitView adjustSubviews];
[_splitView addSubview:_devToolsView];
[_splitView adjustSubviews];
_cefDevToolsClient = new AtomCefClient(true, true);
std::string devtools_url = _cefClient->GetBrowser()->GetHost()->GetDevToolsURL(true);
[self addBrowserToView:_devToolsView url:devtools_url.c_str() cefHandler:_cefDevToolsClient];
}
_cefDevToolsClient = new AtomCefClient(true, true);
std::string devtools_url = _cefClient->GetBrowser()->GetHost()->GetDevToolsURL(true);
[self addBrowserToView:_devToolsView url:devtools_url.c_str() cefHandler:_cefDevToolsClient];
}
- (void)hideDevTools {
@@ -212,17 +215,19 @@
[_devToolsView release];
_devToolsView = nil;
_cefDevToolsClient = NULL;
_cefClient->GetBrowser()->GetHost()->SetFocus(true);
if ([self isBrowserUsable]) {
_cefClient->GetBrowser()->GetHost()->SetFocus(true);
}
}
- (void)openPath:(NSString*)path {
if (_cefClient && _cefClient->GetBrowser()) {
CefRefPtr<CefProcessMessage> openMessage = CefProcessMessage::Create("openPath");
CefRefPtr<CefListValue> openArguments = openMessage->GetArgumentList();
openArguments->SetSize(1);
openArguments->SetString(0, [path UTF8String]);
_cefClient->GetBrowser()->SendProcessMessage(PID_RENDERER, openMessage);
}
if (![self isBrowserUsable]) return;
CefRefPtr<CefProcessMessage> openMessage = CefProcessMessage::Create("openPath");
CefRefPtr<CefListValue> openArguments = openMessage->GetArgumentList();
openArguments->SetSize(1);
openArguments->SetString(0, [path UTF8String]);
_cefClient->GetBrowser()->SendProcessMessage(PID_RENDERER, openMessage);
}
- (void)setPidToKillOnClose:(NSNumber *)pid {
@@ -239,14 +244,15 @@
}
- (void)windowDidBecomeMain:(NSNotification *)notification {
if (_cefClient && _cefClient->GetBrowser()) {
if ([self isBrowserUsable]) {
_cefClient->GetBrowser()->GetHost()->SetFocus(true);
}
}
- (BOOL)windowShouldClose:(NSNotification *)notification {
if (_cefClient && _cefClient->GetBrowser()) {
if ([self isBrowserUsable]) {
_cefClient->GetBrowser()->GetHost()->CloseBrowser(false);
return NO;
}
if (_pidToKillOnClose) kill([_pidToKillOnClose intValue], SIGQUIT);
+8 -3
Ver Arquivo
@@ -5,9 +5,9 @@
"dependencies": {
"coffee-script": "1.6.2",
"ctags": "0.3.0",
"oniguruma": "0.8.0",
"oniguruma": "0.11.0",
"mkdirp": "0.3.5",
"git-utils": "0.13.0",
"git-utils": "0.14.0",
"underscore": "1.4.4",
"d3": "3.0.8",
"coffee-cache": "0.1.0",
@@ -17,7 +17,12 @@
"spellchecker": "0.3.0",
"pathwatcher": "0.3.0",
"plist": "git://github.com/nathansobo/node-plist.git",
"space-pen": "git://github.com/nathansobo/space-pen.git"
"space-pen": "git://github.com/nathansobo/space-pen.git",
"less": "git://github.com/nathansobo/less.js.git"
},
"devDependencies" : {
"biscotto" : "0.0.9"
},
"private": true,
+17 -12
Ver Arquivo
@@ -1,11 +1,4 @@
# Hack since the vendored less is in browser mode
global.window = {}
global.document =
getElementsByTagName: -> []
global.location =
port: 80
{less} = require '../vendor/less'
less = require 'less'
fs = require 'fs'
inputFile = process.argv[2]
@@ -19,7 +12,19 @@ unless outputFile?.length > 0
process.exit(1)
contents = fs.readFileSync(inputFile)?.toString() ? ''
(new less.Parser).parse contents, (e, tree) ->
console.error(e.stack or e) if e
process.exit(1) if e
fs.writeFileSync(outputFile, tree.toCSS())
parser = new less.Parser
syncImport: true
paths: [fs.realpathSync("#{__dirname}/../static"), fs.realpathSync("#{__dirname}/../vendor")]
filename: inputFile
logErrorAndExit = (e) ->
console.error("Error compiling less file '#{inputFile}':", e.message)
process.exit(1)
parser.parse contents, (e, tree) ->
logErrorAndExit(e) if e
try
fs.writeFileSync(outputFile, tree.toCSS())
catch e
logErrorAndExit(e)
+7 -4
Ver Arquivo
@@ -6,10 +6,13 @@ set -e
COMPILED_SOURCES_DIR="${1}"
RESOUCES_PATH="$BUILT_PRODUCTS_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH"
# Copy non-coffee files into bundle
# Copy non-coffee/cson/less files into bundle
rsync --archive --recursive \
--exclude="src/**.coffee" --exclude="src/**.cson" \
--exclude="src/**.less" --exclude="static/**.less" \
--exclude="node_modules/less" \
--exclude="src/**.coffee" \
--exclude="src/**.cson" \
--exclude="themes/**.cson" \
--exclude="src/**.less" \
--exclude="static/**.less" \
--exclude="themes/**.less" \
node_modules src static vendor spec benchmark themes dot-atom atom.sh \
"${COMPILED_SOURCES_DIR}/" "$RESOUCES_PATH"
+3 -9
Ver Arquivo
@@ -4,12 +4,6 @@ set -e
cd "$(dirname $0)/.."
DIRS="src static vendor"
find_files() {
find ${DIRS} -type file -name ${1}
}
file_list() {
while read file; do
echo " '${file}',"
@@ -22,13 +16,13 @@ cat > sources.gypi <<EOF
'compiled_sources_dir': '<(INTERMEDIATE_DIR)/atom-resources',
'compiled_sources_dir_xcode': '\${INTERMEDIATE_DIR}/atom-resources',
'coffee_sources': [
$(find_files '*.coffee' | file_list)
$(find src static vendor -type file -name '*.coffee' | file_list)
],
'cson_sources': [
$(find_files '*.cson' | file_list)
$(find src static themes vendor -type file -name '*.cson' | file_list)
],
'less_sources': [
$(find src static -type file -name '*.less' | file_list)
$(find src static themes -type file -name '*.less' | file_list)
],
},
}
Arquivo executável
+14
Ver Arquivo
@@ -0,0 +1,14 @@
if [ -t 0 ] ; then # If stdout is a terminal
INTERACTIVE=1
fi
polite_curl()
{
if [ $INTERACTIVE ] ; then
CURL_ARGS="--progress-bar"
else
CURL_ARGS="-fsS"
fi
curl $CURL_ARGS $*
}
+23 -30
Ver Arquivo
@@ -1,50 +1,43 @@
#!/bin/bash
cd "$( dirname "${BASH_SOURCE[0]}" )/.."
. $(dirname $0)/lib/polite-curl
cd "$(dirname "${BASH_SOURCE[0]}" )/.."
if [[ $1 == '-s' ]]; then
SYMBOLS=1
SYMBOLS="true"
shift
fi
if [ -z $1 ]; then
TARGET='cef'
else
TARGET=$1
fi
TARGET=${1:-cef}
DISTURL="https://gh-contractor-zcbenz.s3.amazonaws.com/cefode3/prebuilt-cef"
CURRENT_VERSION=$(cat cef/version 2>&1)
LATEST_VERSION=$(curl -fsSkL $DISTURL/version)
TEMP_DIR=$(mktemp -d -t prebuilt-cef-download.XXXXXX)
trap "rm -rf \"${TEMP_DIR}\"" EXIT
# Latest version
if ! LATEST_VERSION=$(curl -fsSkL $DISTURL/version); then
exit 1;
if [ -z "$LATEST_VERSION" ] ; then
echo "Could determine lastest version of cefode" >&2
exit 1
fi
# Current version
CURRENT_VERSION=`cat cef/version 2>&1`
TEMP_DIR=/tmp/atom-cached-cefodes/$LATEST_VERSION
if [[ $LATEST_VERSION != $CURRENT_VERSION ]]; then
echo "Downloading/extracting cefode3 u${LATEST_VERSION}..."
if [ -t 1 ] ; then # If run from the terminal
CURL_ARGS="--progress-bar"
if [ -d $TEMP_DIR ]; then
echo "Using cached version of cefode3 v${LATEST_VERSION} from ${TEMP_DIR}"
else
CURL_ARGS="-fsS"
echo "Downloading/extracting cefode3 v${LATEST_VERSION}..."
mkdir -p $TEMP_DIR
polite_curl "${DISTURL}/cef_binary_latest.zip" > "${TEMP_DIR}/cef.zip"
unzip -q "${TEMP_DIR}/cef.zip" -d "${TEMP_DIR}"
fi
curl $CURL_ARGS "${DISTURL}/cef_binary_latest.zip" > "${TEMP_DIR}/cef.zip"
unzip -q "${TEMP_DIR}/cef.zip" -d "${TEMP_DIR}"
[ -e "${TARGET}" ] && rm -rf "${TARGET}"
mv "${TEMP_DIR}"/*_macosx "${TARGET}"
cp -r "${TEMP_DIR}"/*_macosx "${TARGET}"
echo ${LATEST_VERSION} > 'cef/version'
fi
if [[ "${SYMBOLS}" != "1" ]]; then
exit 0
if [ -n "$SYMBOLS" ]; then
echo "Downloading/extracting symbols for cefode3 u${LATEST_VERSION}..."
polite_curl "${DISTURL}/cef_binary_latest_symbols.zip" > "${TEMP_DIR}/symbols.zip"
unzip -q "${TEMP_DIR}/symbols.zip" -d "${TEMP_DIR}"
mv "${TEMP_DIR}"/*_macosx_symbols/* "${TARGET}/Release"
fi
echo "Downloading/extracting symbols for cefode3 u${LATEST_VERSION}..."
curl --progress-bar "${DISTURL}/cef_binary_latest_symbols.zip" > "${TEMP_DIR}/symbols.zip"
unzip -q "${TEMP_DIR}/symbols.zip" -d "${TEMP_DIR}"
mv "${TEMP_DIR}"/*_macosx_symbols/* "${TARGET}/Release"
+24
Ver Arquivo
@@ -2,6 +2,7 @@ $ = require 'jquery'
RootView = require 'root-view'
{$$} = require 'space-pen'
fsUtils = require 'fs-utils'
Exec = require('child_process').exec
describe "the `atom` global", ->
beforeEach ->
@@ -321,3 +322,26 @@ describe "the `atom` global", ->
expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
expect(atom.sendMessageToBrowserProcess.argsForCall[0][1][0]).toBe "A2"
atom.sendMessageToBrowserProcess.simulateConfirmation('Next')
describe "API documentation", ->
it "meets a minimum threshold for /app (with no errors)", ->
docRunner = jasmine.createSpy("docRunner")
Exec "rake docs:app:stats", cwd: project.resolve('../..'), docRunner
waitsFor ->
docRunner.callCount > 0
runs ->
# error
expect(docRunner.argsForCall[0][0]).toBeNull()
results = docRunner.argsForCall[0][1].split("\n")
results.pop()
errors = parseInt results.pop().match(/\d+/)
expect(errors).toBe 0
coverage = parseFloat results.pop().match(/.+?%/)
expect(coverage).toBeGreaterThan 80
# stderr
expect(docRunner.argsForCall[0][2]).toBe ''
+25 -11
Ver Arquivo
@@ -120,19 +120,33 @@ describe "Config", ->
describe "when the configDirPath doesn't exist", ->
it "copies the contents of dot-atom to ~/.atom", ->
config.initializeConfigDirectory()
expect(fsUtils.exists(config.configDirPath)).toBeTruthy()
expect(fsUtils.exists(fsUtils.join(config.configDirPath, 'packages'))).toBeTruthy()
expect(fsUtils.exists(fsUtils.join(config.configDirPath, 'snippets'))).toBeTruthy()
expect(fsUtils.exists(fsUtils.join(config.configDirPath, 'themes'))).toBeTruthy()
expect(fsUtils.isFile(fsUtils.join(config.configDirPath, 'config.cson'))).toBeTruthy()
initializationDone = false
jasmine.unspy(window, "setTimeout")
config.initializeConfigDirectory ->
initializationDone = true
waitsFor -> initializationDone
runs ->
expect(fsUtils.exists(config.configDirPath)).toBeTruthy()
expect(fsUtils.exists(fsUtils.join(config.configDirPath, 'packages'))).toBeTruthy()
expect(fsUtils.exists(fsUtils.join(config.configDirPath, 'snippets'))).toBeTruthy()
expect(fsUtils.exists(fsUtils.join(config.configDirPath, 'themes'))).toBeTruthy()
expect(fsUtils.isFile(fsUtils.join(config.configDirPath, 'config.cson'))).toBeTruthy()
it "copies the bundles themes to ~/.atom", ->
config.initializeConfigDirectory()
expect(fsUtils.isFile(fsUtils.join(config.configDirPath, 'themes/atom-dark-ui/package.cson'))).toBeTruthy()
expect(fsUtils.isFile(fsUtils.join(config.configDirPath, 'themes/atom-light-ui/package.cson'))).toBeTruthy()
expect(fsUtils.isFile(fsUtils.join(config.configDirPath, 'themes/atom-dark-syntax.css'))).toBeTruthy()
expect(fsUtils.isFile(fsUtils.join(config.configDirPath, 'themes/atom-light-syntax.css'))).toBeTruthy()
initializationDone = false
jasmine.unspy(window, "setTimeout")
config.initializeConfigDirectory ->
initializationDone = true
waitsFor -> initializationDone
runs ->
expect(fsUtils.isFile(fsUtils.join(config.configDirPath, 'themes/atom-dark-ui/package.cson'))).toBeTruthy()
expect(fsUtils.isFile(fsUtils.join(config.configDirPath, 'themes/atom-light-ui/package.cson'))).toBeTruthy()
expect(fsUtils.isFile(fsUtils.join(config.configDirPath, 'themes/atom-dark-syntax.css'))).toBeTruthy()
expect(fsUtils.isFile(fsUtils.join(config.configDirPath, 'themes/atom-light-syntax.css'))).toBeTruthy()
describe "when the config file is not parseable", ->
beforeEach ->
+17 -5
Ver Arquivo
@@ -22,9 +22,9 @@ describe "Editor", ->
editor.isFocused = true
editor.enableKeymap()
editor.attachToDom = ({ heightInLines, widthInChars } = {}) ->
heightInLines ?= this.getBuffer().getLineCount()
this.height(getLineHeight() * heightInLines)
this.width(getCharWidth() * widthInChars) if widthInChars
heightInLines ?= @getBuffer().getLineCount()
@height(getLineHeight() * heightInLines)
@width(getCharWidth() * widthInChars) if widthInChars
$('#jasmine-content').append(this)
getLineHeight = ->
@@ -799,7 +799,7 @@ describe "Editor", ->
setEditorHeightInLines(editor, 4)
describe "if autoscroll is true", ->
it "centers the viewport on the selection if its vertical center is currently offsUtilscreen", ->
it "centers the viewport on the selection if its vertical center is currently offscreen", ->
editor.setSelectedBufferRange([[2, 0], [4, 0]], autoscroll: true)
expect(editor.scrollTop()).toBe 0
@@ -1210,7 +1210,7 @@ describe "Editor", ->
expect(editor.renderedLines.find('.line:last').text()).toBe buffer.lineForRow(7)
describe "when scrolling more than the editors height", ->
it "removes lines that are offsUtilscreen and not in range of the overdraw and builds lines that become visible", ->
it "removes lines that are offscreen and not in range of the overdraw and builds lines that become visible", ->
editor.scrollTop(editor.scrollView.prop('scrollHeight') - editor.scrollView.height())
expect(editor.renderedLines.find('.line').length).toBe 8
expect(editor.renderedLines.find('.line:first').text()).toBe buffer.lineForRow(5)
@@ -2575,3 +2575,15 @@ describe "Editor", ->
editor.on 'editor:will-be-removed', willBeRemovedHandler
editor.getPane().destroyActiveItem()
expect(willBeRemovedHandler).toHaveBeenCalled()
describe "when setInvisibles is toggled (regression)", ->
it "renders inserted newlines properly", ->
editor.setShowInvisibles(true)
editor.setCursorBufferPosition([0, 0])
editor.attachToDom(heightInLines: 20)
editor.setShowInvisibles(false)
editor.insertText("\n")
for rowNumber in [1..5]
expect(editor.lineElementForScreenRow(rowNumber).text()).toBe buffer.lineForRow(rowNumber)
+39
Ver Arquivo
@@ -16,6 +16,26 @@ describe "EventEmitter mixin", ->
object.on 'foo', fooHandler2
object.on 'bar', barHandler
describe ".on", ->
describe "when called with multiple space-separated event names", ->
it "subscribes to each event names", ->
object.on ' a.b c.d\te ', fooHandler1
object.trigger 'a'
expect(fooHandler1).toHaveBeenCalled()
fooHandler1.reset()
object.trigger 'c'
expect(fooHandler1).toHaveBeenCalled()
fooHandler1.reset()
object.trigger 'e'
expect(fooHandler1).toHaveBeenCalled()
fooHandler1.reset()
object.trigger ''
expect(fooHandler1).not.toHaveBeenCalled()
describe ".trigger", ->
describe "when called with a non-namespaced event name", ->
it "triggers all handlers registered for the given event name", ->
@@ -59,6 +79,22 @@ describe "EventEmitter mixin", ->
expect(fooHandler1).not.toHaveBeenCalled()
expect(fooHandler2).not.toHaveBeenCalled()
describe "when called with multiple space-separated event names", ->
it "unsubscribes from each event name", ->
object.on 'a.b c.d e', fooHandler1
object.off ' a.b\te '
object.trigger 'a'
expect(fooHandler1).not.toHaveBeenCalled()
fooHandler1.reset()
object.trigger 'e'
expect(fooHandler1).not.toHaveBeenCalled()
fooHandler1.reset()
object.trigger 'c.d'
expect(fooHandler1).toHaveBeenCalled()
describe "when called with a non-namespaced event name", ->
it "removes all handlers for that event name", ->
object.off 'foo'
@@ -73,6 +109,9 @@ describe "EventEmitter mixin", ->
expect(fooHandler1).not.toHaveBeenCalled()
expect(fooHandler2).toHaveBeenCalled()
it "does not throw an exception if there was not matching `on` call", ->
expect(-> object.off 'marco', -> "nothing").not.toThrow()
describe "when there are namespaced event handlers", ->
[barHandler2, bazHandler1, bazHandler2, bazHandler3] = []
+1 -1
Ver Arquivo
@@ -8,7 +8,7 @@ describe "ImageView", ->
path = project.resolve('binary-file.png')
view = new ImageView()
view.attachToDom()
view.parent().height(100)
view.height(100)
it "displays the image for a path", ->
view.setModel(new ImageEditSession(path))
+16
Ver Arquivo
@@ -284,6 +284,22 @@ describe "Project", ->
expect(paths.length).toBe 0
expect(matches.length).toBe 0
it "includes files and folders that begin with a '.'", ->
projectPath = '/tmp/atom-tests/folder-with-dot-file'
filePath = fsUtils.join(projectPath, '.text')
fsUtils.write(filePath, 'match this')
project.setPath(projectPath)
paths = []
matches = []
waitsForPromise ->
project.scan /match this/, ({path, match, range}) ->
paths.push(path)
matches.push(match)
runs ->
expect(paths.length).toBe 1
expect(paths[0]).toBe filePath
expect(matches.length).toBe 1
describe "serialization", ->
it "restores the project path", ->
+1 -1
Ver Arquivo
@@ -113,7 +113,7 @@ describe "SelectList", ->
miniEditor.trigger 'core:move-up'
miniEditor.trigger 'core:move-up'
expect(list.scrollBottom()).toBe itemHeight * 3
expect(list.scrollTop()).toBe itemHeight
describe "the core:confirm event", ->
describe "when there is an item selected (because the list in not empty)", ->
+207 -28
Ver Arquivo
@@ -12,6 +12,8 @@ describe "TextMateGrammar", ->
atom.activatePackage('javascript.tmbundle', sync: true)
atom.activatePackage('coffee-script-tmbundle', sync: true)
atom.activatePackage('ruby.tmbundle', sync: true)
atom.activatePackage('html.tmbundle', sync: true)
atom.activatePackage('php.tmbundle', sync: true)
grammar = syntax.selectGrammar("hello.coffee")
describe "@loadSync(path)", ->
@@ -208,36 +210,80 @@ describe "TextMateGrammar", ->
expect(tokens[11]).toEqual value: ' damn.', scopes: ["source.ruby","comment.line.number-sign.ruby"]
describe "when the pattern includes rules from another grammar", ->
it "parses tokens inside the begin/end patterns based on the included grammar's rules", ->
atom.activatePackage('html.tmbundle', sync: true)
atom.activatePackage('ruby-on-rails-tmbundle', sync: true)
describe "when a grammar matching the desired scope is available", ->
it "parses tokens inside the begin/end patterns based on the included grammar's rules", ->
atom.activatePackage('html.tmbundle', sync: true)
atom.activatePackage('ruby-on-rails-tmbundle', sync: true)
grammar = syntax.selectGrammar('foo.html.erb')
{tokens} = grammar.tokenizeLine("<div class='name'><%= User.find(2).full_name %></div>")
grammar = syntax.selectGrammar('foo.html.erb')
{tokens} = grammar.tokenizeLine("<div class='name'><%= User.find(2).full_name %></div>")
expect(tokens[0]).toEqual value: '<', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.begin.html"]
expect(tokens[1]).toEqual value: 'div', scopes: ["text.html.ruby","meta.tag.block.any.html","entity.name.tag.block.any.html"]
expect(tokens[2]).toEqual value: ' ', scopes: ["text.html.ruby","meta.tag.block.any.html"]
expect(tokens[3]).toEqual value: 'class', scopes: ["text.html.ruby","meta.tag.block.any.html", "entity.other.attribute-name.html"]
expect(tokens[4]).toEqual value: '=', scopes: ["text.html.ruby","meta.tag.block.any.html"]
expect(tokens[5]).toEqual value: '\'', scopes: ["text.html.ruby","meta.tag.block.any.html","string.quoted.single.html","punctuation.definition.string.begin.html"]
expect(tokens[6]).toEqual value: 'name', scopes: ["text.html.ruby","meta.tag.block.any.html","string.quoted.single.html"]
expect(tokens[7]).toEqual value: '\'', scopes: ["text.html.ruby","meta.tag.block.any.html","string.quoted.single.html","punctuation.definition.string.end.html"]
expect(tokens[8]).toEqual value: '>', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.end.html"]
expect(tokens[9]).toEqual value: '<%=', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.embedded.ruby"]
expect(tokens[10]).toEqual value: ' ', scopes: ["text.html.ruby","source.ruby.rails.embedded.html"]
expect(tokens[11]).toEqual value: 'User', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","support.class.ruby"]
expect(tokens[12]).toEqual value: '.', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.separator.method.ruby"]
expect(tokens[13]).toEqual value: 'find', scopes: ["text.html.ruby","source.ruby.rails.embedded.html"]
expect(tokens[14]).toEqual value: '(', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.function.ruby"]
expect(tokens[15]).toEqual value: '2', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","constant.numeric.ruby"]
expect(tokens[16]).toEqual value: ')', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.function.ruby"]
expect(tokens[17]).toEqual value: '.', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.separator.method.ruby"]
expect(tokens[18]).toEqual value: 'full_name ', scopes: ["text.html.ruby","source.ruby.rails.embedded.html"]
expect(tokens[19]).toEqual value: '%>', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.embedded.ruby"]
expect(tokens[20]).toEqual value: '</', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.begin.html"]
expect(tokens[21]).toEqual value: 'div', scopes: ["text.html.ruby","meta.tag.block.any.html","entity.name.tag.block.any.html"]
expect(tokens[22]).toEqual value: '>', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.end.html"]
expect(tokens[0]).toEqual value: '<', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.begin.html"]
expect(tokens[1]).toEqual value: 'div', scopes: ["text.html.ruby","meta.tag.block.any.html","entity.name.tag.block.any.html"]
expect(tokens[2]).toEqual value: ' ', scopes: ["text.html.ruby","meta.tag.block.any.html"]
expect(tokens[3]).toEqual value: 'class', scopes: ["text.html.ruby","meta.tag.block.any.html", "entity.other.attribute-name.html"]
expect(tokens[4]).toEqual value: '=', scopes: ["text.html.ruby","meta.tag.block.any.html"]
expect(tokens[5]).toEqual value: '\'', scopes: ["text.html.ruby","meta.tag.block.any.html","string.quoted.single.html","punctuation.definition.string.begin.html"]
expect(tokens[6]).toEqual value: 'name', scopes: ["text.html.ruby","meta.tag.block.any.html","string.quoted.single.html"]
expect(tokens[7]).toEqual value: '\'', scopes: ["text.html.ruby","meta.tag.block.any.html","string.quoted.single.html","punctuation.definition.string.end.html"]
expect(tokens[8]).toEqual value: '>', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.end.html"]
expect(tokens[9]).toEqual value: '<%=', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.embedded.ruby"]
expect(tokens[10]).toEqual value: ' ', scopes: ["text.html.ruby","source.ruby.rails.embedded.html"]
expect(tokens[11]).toEqual value: 'User', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","support.class.ruby"]
expect(tokens[12]).toEqual value: '.', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.separator.method.ruby"]
expect(tokens[13]).toEqual value: 'find', scopes: ["text.html.ruby","source.ruby.rails.embedded.html"]
expect(tokens[14]).toEqual value: '(', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.function.ruby"]
expect(tokens[15]).toEqual value: '2', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","constant.numeric.ruby"]
expect(tokens[16]).toEqual value: ')', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.function.ruby"]
expect(tokens[17]).toEqual value: '.', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.separator.method.ruby"]
expect(tokens[18]).toEqual value: 'full_name ', scopes: ["text.html.ruby","source.ruby.rails.embedded.html"]
expect(tokens[19]).toEqual value: '%>', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.embedded.ruby"]
expect(tokens[20]).toEqual value: '</', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.begin.html"]
expect(tokens[21]).toEqual value: 'div', scopes: ["text.html.ruby","meta.tag.block.any.html","entity.name.tag.block.any.html"]
expect(tokens[22]).toEqual value: '>', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.end.html"]
it "updates the grammar if the included grammar is updated later", ->
atom.activatePackage('html.tmbundle', sync: true)
atom.activatePackage('ruby-on-rails-tmbundle', sync: true)
grammar = syntax.selectGrammar('foo.html.erb')
grammarUpdatedHandler = jasmine.createSpy("grammarUpdatedHandler")
grammar.on 'grammar-updated', grammarUpdatedHandler
{tokens} = grammar.tokenizeLine("<div class='name'><% <<-SQL select * from users;")
expect(tokens[12].value).toBe " select * from users;"
atom.activatePackage('sql.tmbundle', sync: true)
expect(grammarUpdatedHandler).toHaveBeenCalled()
{tokens} = grammar.tokenizeLine("<div class='name'><% <<-SQL select * from users;")
expect(tokens[12].value).toBe " "
expect(tokens[13].value).toBe "select"
describe "when a grammar matching the desired scope is unavailable", ->
it "updates the grammar if a matching grammar is added later", ->
atom.deactivatePackage('html.tmbundle')
atom.activatePackage('ruby-on-rails-tmbundle', sync: true)
grammar = syntax.selectGrammar('foo.html.erb')
{tokens} = grammar.tokenizeLine("<div class='name'><%= User.find(2).full_name %></div>")
expect(tokens[0]).toEqual value: "<div class='name'>", scopes: ["text.html.ruby"]
expect(tokens[1]).toEqual value: '<%=', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.embedded.ruby"]
expect(tokens[2]).toEqual value: ' ', scopes: ["text.html.ruby","source.ruby.rails.embedded.html"]
expect(tokens[3]).toEqual value: 'User', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","support.class.ruby"]
atom.activatePackage('html.tmbundle', sync: true)
{tokens} = grammar.tokenizeLine("<div class='name'><%= User.find(2).full_name %></div>")
expect(tokens[0]).toEqual value: '<', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.begin.html"]
expect(tokens[1]).toEqual value: 'div', scopes: ["text.html.ruby","meta.tag.block.any.html","entity.name.tag.block.any.html"]
expect(tokens[2]).toEqual value: ' ', scopes: ["text.html.ruby","meta.tag.block.any.html"]
expect(tokens[3]).toEqual value: 'class', scopes: ["text.html.ruby","meta.tag.block.any.html", "entity.other.attribute-name.html"]
expect(tokens[4]).toEqual value: '=', scopes: ["text.html.ruby","meta.tag.block.any.html"]
expect(tokens[5]).toEqual value: '\'', scopes: ["text.html.ruby","meta.tag.block.any.html","string.quoted.single.html","punctuation.definition.string.begin.html"]
expect(tokens[6]).toEqual value: 'name', scopes: ["text.html.ruby","meta.tag.block.any.html","string.quoted.single.html"]
expect(tokens[7]).toEqual value: '\'', scopes: ["text.html.ruby","meta.tag.block.any.html","string.quoted.single.html","punctuation.definition.string.end.html"]
expect(tokens[8]).toEqual value: '>', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.end.html"]
expect(tokens[9]).toEqual value: '<%=', scopes: ["text.html.ruby","source.ruby.rails.embedded.html","punctuation.section.embedded.ruby"]
expect(tokens[10]).toEqual value: ' ', scopes: ["text.html.ruby","source.ruby.rails.embedded.html"]
it "can parse a grammar with newline characters in its regular expressions (regression)", ->
grammar = new TextMateGrammar
@@ -308,3 +354,136 @@ describe "TextMateGrammar", ->
expect(tokens.length).toBe 5
expect(tokens[4].value).toBe "three(four(five(_param_)))))"
expect(ruleStack).toEqual originalRuleStack
describe "when a grammar has a capture with patterns", ->
it "matches the patterns and includes the scope specified as the pattern's match name", ->
grammar = syntax.selectGrammar("hello.php")
{tokens} = grammar.tokenizeLine("<?php public final function meth() {} ?>")
expect(tokens[2].value).toBe "public"
expect(tokens[2].scopes).toEqual ["text.html.php", "meta.embedded.line.php", "source.php", "meta.function.php", "storage.modifier.php"]
expect(tokens[3].value).toBe " "
expect(tokens[3].scopes).toEqual ["text.html.php", "meta.embedded.line.php", "source.php", "meta.function.php"]
expect(tokens[4].value).toBe "final"
expect(tokens[4].scopes).toEqual ["text.html.php", "meta.embedded.line.php", "source.php", "meta.function.php", "storage.modifier.php"]
expect(tokens[5].value).toBe " "
expect(tokens[5].scopes).toEqual ["text.html.php", "meta.embedded.line.php", "source.php", "meta.function.php"]
expect(tokens[6].value).toBe "function"
expect(tokens[6].scopes).toEqual ["text.html.php", "meta.embedded.line.php", "source.php", "meta.function.php", "storage.type.function.php"]
it "ignores child captures of a capture with patterns", ->
grammar = new TextMateGrammar
name: "test"
scopeName: "source"
repository: {}
patterns: [
{
name: "text"
match: "(a(b))"
captures:
"1":
patterns: [
{
match: "ab"
name: "a"
}
]
"2":
name: "b"
}
]
{tokens} = grammar.tokenizeLine("ab")
expect(tokens[0].value).toBe "ab"
expect(tokens[0].scopes).toEqual ["source", "text", "a"]
describe "when the grammar has injections", ->
it "correctly includes the injected patterns when tokenizing", ->
grammar = syntax.selectGrammar("hello.php")
{tokens} = grammar.tokenizeLine("<div><?php function hello() {} ?></div>")
expect(tokens[3].value).toBe "<?php"
expect(tokens[3].scopes).toEqual ["text.html.php", "meta.embedded.line.php", "punctuation.section.embedded.begin.php"]
expect(tokens[5].value).toBe "function"
expect(tokens[5].scopes).toEqual ["text.html.php", "meta.embedded.line.php", "source.php", "meta.function.php", "storage.type.function.php"]
expect(tokens[7].value).toBe "hello"
expect(tokens[7].scopes).toEqual ["text.html.php", "meta.embedded.line.php", "source.php", "meta.function.php", "entity.name.function.php"]
expect(tokens[14].value).toBe "?"
expect(tokens[14].scopes).toEqual ["text.html.php", "meta.embedded.line.php", "source.php", "punctuation.section.embedded.end.php", "source.php"]
expect(tokens[15].value).toBe ">"
expect(tokens[15].scopes).toEqual ["text.html.php", "meta.embedded.line.php", "punctuation.section.embedded.end.php"]
expect(tokens[16].value).toBe "</"
expect(tokens[16].scopes).toEqual ["text.html.php", "meta.tag.block.any.html", "punctuation.definition.tag.begin.html"]
expect(tokens[17].value).toBe "div"
expect(tokens[17].scopes).toEqual ["text.html.php", "meta.tag.block.any.html", "entity.name.tag.block.any.html"]
describe "when the grammar's pattern name has a group number in it", ->
it "replaces the group number with the matched captured text", ->
atom.activatePackage('hyperlink-helper.tmbundle', sync: true)
grammar = syntax.grammarForScopeName("text.hyperlink")
{tokens} = grammar.tokenizeLine("https://github.com")
expect(tokens[0].scopes).toEqual ["text.hyperlink", "markup.underline.link.https.hyperlink"]
describe "when the grammar has an injection selector", ->
it "includes the grammar's patterns when the selector matches the current scope in other grammars", ->
atom.activatePackage('hyperlink-helper.tmbundle', sync: true)
grammar = syntax.selectGrammar("text.js")
{tokens} = grammar.tokenizeLine("var i; // http://github.com")
expect(tokens[0].value).toBe "var";
expect(tokens[0].scopes).toEqual ["source.js", "storage.modifier.js"]
expect(tokens[6].value).toBe "http://github.com"
expect(tokens[6].scopes).toEqual ["source.js", "comment.line.double-slash.js", "markup.underline.link.http.hyperlink"]
describe "when the grammar is added", ->
it "retokenizes existing buffers that contain tokens that match the injection selector", ->
editSession = project.buildEditSession('sample.js')
editSession.setText("// http://github.com")
{tokens} = editSession.lineForScreenRow(0)
expect(tokens[1].value).toBe " http://github.com"
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
atom.activatePackage('hyperlink-helper.tmbundle', sync: true)
{tokens} = editSession.lineForScreenRow(0)
expect(tokens[2].value).toBe "http://github.com"
expect(tokens[2].scopes).toEqual ["source.js", "comment.line.double-slash.js", "markup.underline.link.http.hyperlink"]
describe "when the grammar is updated", ->
it "retokenizes existing buffers that contain tokens that match the injection selector", ->
editSession = project.buildEditSession('sample.js')
editSession.setText("// SELECT * FROM OCTOCATS")
{tokens} = editSession.lineForScreenRow(0)
expect(tokens[1].value).toBe " SELECT * FROM OCTOCATS"
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
syntax.addGrammar(new TextMateGrammar(
name: "test"
scopeName: "source.test"
repository: {}
injectionSelector: "comment"
patterns: [ { include: "source.sql" } ]
))
{tokens} = editSession.lineForScreenRow(0)
expect(tokens[1].value).toBe " SELECT * FROM OCTOCATS"
expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
atom.activatePackage('sql.tmbundle', sync: true)
{tokens} = editSession.lineForScreenRow(0)
expect(tokens[2].value).toBe "SELECT"
expect(tokens[2].scopes).toEqual ["source.js", "comment.line.double-slash.js", "keyword.other.DML.sql"]
@@ -0,0 +1,46 @@
TextMateScopeSelector = require 'text-mate-scope-selector'
describe "TextMateScopeSelector", ->
it "matches the asterix", ->
expect(new TextMateScopeSelector('*').matches(['a'])).toBeTruthy()
expect(new TextMateScopeSelector('*').matches(['b', 'c'])).toBeTruthy()
expect(new TextMateScopeSelector('a.*.c').matches(['a.b.c'])).toBeTruthy()
expect(new TextMateScopeSelector('a.*.c').matches(['a.b.c.d'])).toBeTruthy()
expect(new TextMateScopeSelector('a.*.c').matches(['a.b.d.c'])).toBeFalsy()
it "matches prefixes", ->
expect(new TextMateScopeSelector('a').matches(['a'])).toBeTruthy()
expect(new TextMateScopeSelector('a').matches(['a.b'])).toBeTruthy()
expect(new TextMateScopeSelector('a').matches(['abc'])).toBeFalsy()
it "matches disjunction", ->
expect(new TextMateScopeSelector('a | b').matches(['b'])).toBeTruthy()
expect(new TextMateScopeSelector('a|b|c').matches(['c'])).toBeTruthy()
expect(new TextMateScopeSelector('a|b|c').matches(['d'])).toBeFalsy()
it "matches negation", ->
expect(new TextMateScopeSelector('a - c').matches(['a', 'b'])).toBeTruthy()
expect(new TextMateScopeSelector('a-b').matches(['a', 'b'])).toBeFalsy()
it "matches conjunction", ->
expect(new TextMateScopeSelector('a & b').matches(['b', 'a'])).toBeTruthy()
expect(new TextMateScopeSelector('a&b&c').matches(['c'])).toBeFalsy()
expect(new TextMateScopeSelector('a&b&c').matches(['a', 'b', 'd'])).toBeFalsy()
it "matches composites", ->
expect(new TextMateScopeSelector('a,b,c').matches(['b', 'c'])).toBeTruthy()
expect(new TextMateScopeSelector('a, b, c').matches(['d', 'e'])).toBeFalsy()
expect(new TextMateScopeSelector('a, b, c').matches(['d', 'c.e'])).toBeTruthy()
it "matches groups", ->
expect(new TextMateScopeSelector('(a,b) | (c, d)').matches(['a'])).toBeTruthy()
expect(new TextMateScopeSelector('(a,b) | (c, d)').matches(['b'])).toBeTruthy()
expect(new TextMateScopeSelector('(a,b) | (c, d)').matches(['c'])).toBeTruthy()
expect(new TextMateScopeSelector('(a,b) | (c, d)').matches(['d'])).toBeTruthy()
expect(new TextMateScopeSelector('(a,b) | (c, d)').matches(['e'])).toBeFalsy()
it "matches paths", ->
expect(new TextMateScopeSelector('a b').matches(['a', 'b'])).toBeTruthy()
expect(new TextMateScopeSelector('a b').matches(['b', 'a'])).toBeFalsy()
expect(new TextMateScopeSelector('a c').matches(['a', 'b', 'c', 'd', 'e'])).toBeTruthy()
expect(new TextMateScopeSelector('a b e').matches(['a', 'b', 'c', 'd', 'e'])).toBeTruthy()
+20
Ver Arquivo
@@ -328,6 +328,26 @@ describe "TokenizedBuffer", ->
expect(tokenizedBuffer.lineForScreenRow(2).text).toBe "#{tabAsSpaces} buy()#{tabAsSpaces}while supply > demand"
describe "when the language mode emits a 'grammar-updated' event based on an included grammar being activated", ->
it "retokenizes the buffer", ->
atom.activatePackage('ruby.tmbundle', sync: true)
atom.activatePackage('ruby-on-rails-tmbundle', sync: true)
editSession = project.buildEditSession()
editSession.setVisible(true)
editSession.setGrammar(syntax.selectGrammar('test.erb'))
editSession.buffer.setText("<div class='name'><%= User.find(2).full_name %></div>")
tokenizedBuffer = editSession.displayBuffer.tokenizedBuffer
fullyTokenize(tokenizedBuffer)
{tokens} = tokenizedBuffer.lineForScreenRow(0)
expect(tokens[0]).toEqual value: "<div class='name'>", scopes: ["text.html.ruby"]
atom.activatePackage('html.tmbundle', sync: true)
fullyTokenize(tokenizedBuffer)
{tokens} = tokenizedBuffer.lineForScreenRow(0)
expect(tokens[0]).toEqual value: '<', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.begin.html"]
describe "when a Git commit message file is tokenized", ->
beforeEach ->
atom.activatePackage('git.tmbundle', sync: true)
+6 -2
Ver Arquivo
@@ -165,8 +165,12 @@ describe "Window", ->
it "copies atom.sh to the specified path", ->
expect(fsUtils.exists(commandPath)).toBeFalsy()
window.installAtomCommand(commandPath)
expect(fsUtils.exists(commandPath)).toBeTruthy()
expect(fsUtils.read(commandPath).length).toBeGreaterThan 1
waitsFor ->
fsUtils.exists(commandPath)
runs ->
expect(fsUtils.read(commandPath).length).toBeGreaterThan 1
describe ".deserialize(state)", ->
class Foo
+1 -1
Ver Arquivo
@@ -8,7 +8,7 @@ class AtomReporter extends View
@div id: 'HTMLReporter', class: 'jasmine_reporter', =>
@div outlet: 'specPopup', class: "spec-popup"
@div outlet: "suites"
@ul outlet: "symbolSummary", class: 'symbolSummary'
@ul outlet: "symbolSummary", class: 'symbolSummary list-unstyled'
@div outlet: "status", class: 'status', =>
@div outlet: "time", class: 'time'
@div outlet: "specCount", class: 'spec-count'
+42
Ver Arquivo
@@ -0,0 +1,42 @@
Subscriber = require 'subscriber'
EventEmitter = require 'event-emitter'
_ = require 'underscore'
describe "Subscriber", ->
[emitter1, emitter2, event1Handler, event2Handler, subscriber] = []
class TestEventEmitter
_.extend TestEventEmitter.prototype, EventEmitter
class TestSubscriber
_.extend TestSubscriber.prototype, Subscriber
beforeEach ->
emitter1 = new TestEventEmitter
emitter2 = new TestEventEmitter
subscriber = new TestSubscriber
event1Handler = jasmine.createSpy("event1Handler")
event2Handler = jasmine.createSpy("event2Handler")
subscriber.subscribe emitter1, 'event1', event1Handler
subscriber.subscribe emitter2, 'event2', event2Handler
it "subscribes to events on the specified object", ->
emitter1.trigger 'event1', 'foo'
expect(event1Handler).toHaveBeenCalledWith('foo')
emitter2.trigger 'event2', 'bar'
expect(event2Handler).toHaveBeenCalledWith('bar')
it "allows an object to unsubscribe en-masse", ->
subscriber.unsubscribe()
emitter1.trigger 'event1', 'foo'
emitter2.trigger 'event2', 'bar'
expect(event1Handler).not.toHaveBeenCalled()
expect(event2Handler).not.toHaveBeenCalled()
it "allows an object to unsubscribe from a specific object", ->
subscriber.unsubscribe(emitter1)
emitter1.trigger 'event1', 'foo'
emitter2.trigger 'event2', 'bar'
expect(event1Handler).not.toHaveBeenCalled()
expect(event2Handler).toHaveBeenCalledWith('bar')
@@ -53,4 +53,15 @@ describe "underscore extensions", ->
expect(_.underscore("CoreyDaleJohnson")).toBe "corey_dale_johnson"
expect(_.underscore("corey_dale_johnson")).toBe "corey_dale_johnson"
describe "spliceWithArray(originalArray, start, length, insertedArray, chunkSize)", ->
describe "when the inserted array is smaller than the chunk size", ->
it "splices the array in place", ->
array = ['a', 'b', 'c']
_.spliceWithArray(array, 1, 1, ['v', 'w', 'x', 'y', 'z'], 100)
expect(array).toEqual ['a', 'v', 'w', 'x', 'y', 'z', 'c']
describe "when the inserted array is larger than the chunk size", ->
it "splices the array in place one chunk at a time (to avoid stack overflows)", ->
array = ['a', 'b', 'c']
_.spliceWithArray(array, 1, 1, ['v', 'w', 'x', 'y', 'z'], 2)
expect(array).toEqual ['a', 'v', 'w', 'x', 'y', 'z', 'c']
+12 -7
Ver Arquivo
@@ -5,6 +5,11 @@ _ = require 'underscore'
$ = require 'jquery'
CSON = require 'cson'
###
# Internal: Loads and resolves packages. #
###
module.exports =
class AtomPackage extends Package
metadata: null
@@ -28,7 +33,7 @@ class AtomPackage extends Package
else
@requireMainModule()
catch e
console.warn "Failed to load package named '#{@name}'", e.stack
console.warn "Failed to load package named '#{@name}'", e.stack ? e
this
activate: ({immediate}={}) ->
@@ -51,7 +56,7 @@ class AtomPackage extends Package
console.warn "Failed to activate package named '#{@name}'", e.stack
loadMetadata: ->
if metadataPath = fsUtils.resolveExtension(fsUtils.join(@path, 'package'), ['cson', 'json'])
if metadataPath = fsUtils.resolveExtension(fsUtils.join(@path, 'package'), ['json', 'cson'])
@metadata = CSON.readObject(metadataPath)
@metadata ?= {}
@@ -61,9 +66,9 @@ class AtomPackage extends Package
getKeymapPaths: ->
keymapsDirPath = fsUtils.join(@path, 'keymaps')
if @metadata.keymaps
@metadata.keymaps.map (name) -> fsUtils.resolve(keymapsDirPath, name, ['cson', 'json', ''])
@metadata.keymaps.map (name) -> fsUtils.resolve(keymapsDirPath, name, ['json', 'cson', ''])
else
fsUtils.list(keymapsDirPath, ['cson', 'json']) ? []
fsUtils.list(keymapsDirPath, ['cson', 'json'])
loadStylesheets: ->
@stylesheets = @getStylesheetPaths().map (path) -> [path, loadStylesheet(path)]
@@ -73,18 +78,18 @@ class AtomPackage extends Package
if @metadata.stylesheets
@metadata.stylesheets.map (name) -> fsUtils.resolve(stylesheetDirPath, name, ['css', 'less', ''])
else
fsUtils.list(stylesheetDirPath, ['css', 'less']) ? []
fsUtils.list(stylesheetDirPath, ['css', 'less'])
loadGrammars: ->
@grammars = []
grammarsDirPath = fsUtils.join(@path, 'grammars')
for grammarPath in fsUtils.list(grammarsDirPath, ['.cson', '.json']) ? []
for grammarPath in fsUtils.list(grammarsDirPath, ['.json', '.cson'])
@grammars.push(TextMateGrammar.loadSync(grammarPath))
loadScopedProperties: ->
@scopedProperties = []
scopedPropertiessDirPath = fsUtils.join(@path, 'scoped-properties')
for scopedPropertiesPath in fsUtils.list(scopedPropertiessDirPath, ['.cson', '.json']) ? []
for scopedPropertiesPath in fsUtils.list(scopedPropertiessDirPath, ['.json', '.cson'])
for selector, properties of fsUtils.readObject(scopedPropertiesPath)
@scopedProperties.push([scopedPropertiesPath, selector, properties])
+6 -2
Ver Arquivo
@@ -1,20 +1,24 @@
fsUtils = require 'fs-utils'
Theme = require 'theme'
CSON = require 'cson'
# Internal: Represents a theme that Atom can use.
module.exports =
class AtomTheme extends Theme
# Internal: Given a path, this loads it as a stylesheet.
#
# stylesheetPath - A {String} to a stylesheet
loadStylesheet: (stylesheetPath)->
@stylesheets[stylesheetPath] = window.loadStylesheet(stylesheetPath)
# Internal: Loads the stylesheets found in a `package.cson` file.
load: ->
if fsUtils.extension(@path) in ['.css', '.less']
@loadStylesheet(@path)
else
metadataPath = fsUtils.resolveExtension(fsUtils.join(@path, 'package'), ['cson', 'json'])
if fsUtils.isFile(metadataPath)
stylesheetNames = CSON.readObject(metadataPath)?.stylesheets
stylesheetNames = fsUtils.readObject(metadataPath)?.stylesheets
if stylesheetNames
for name in stylesheetNames
filename = fsUtils.resolveExtension(fsUtils.join(@path, name), ['.css', '.less', ''])
+4
Ver Arquivo
@@ -5,6 +5,10 @@ fsUtils = require 'fs-utils'
Specificity = require 'specificity'
PEG = require 'pegjs'
###
# Internal #
###
module.exports =
class BindingSet
+7 -2
Ver Arquivo
@@ -1,6 +1,10 @@
Range = require 'range'
_ = require 'underscore'
###
# Internal #
###
module.exports =
class BufferChangeOperation
buffer: null
@@ -64,8 +68,9 @@ class BufferChangeOperation
normalizeLineEndings = @options.normalizeLineEndings ? true
if normalizeLineEndings and suggestedLineEnding = @buffer.suggestedLineEndingForRow(startRow)
lineEndings[index] = suggestedLineEnding for index in [0..lastLineIndex]
@buffer.lines[startRow..endRow] = lines
@buffer.lineEndings[startRow..endRow] = lineEndings
_.spliceWithArray(@buffer.lines, startRow, endRow - startRow + 1, lines)
_.spliceWithArray(@buffer.lineEndings, startRow, endRow - startRow + 1, lineEndings)
@buffer.cachedMemoryContents = null
@buffer.conflict = false if @buffer.conflict and !@buffer.isModified()
+81 -15
Ver Arquivo
@@ -10,10 +10,23 @@ class BufferMarker
suppressObserverNotification: false
invalidationStrategy: null
###
# Internal #
###
constructor: ({@id, @buffer, range, @invalidationStrategy, noTail, reverse}) ->
@invalidationStrategy ?= 'contains'
@setRange(range, {noTail, reverse})
###
# Public #
###
# Public: Sets the marker's range, potentialy modifying both its head and tail.
#
# range - The new {Range} the marker should cover
# options - A hash of options with the following keys:
# :reverse - if `true`, the marker is reversed; that is, its tail is "above" the head
# :noTail - if `true`, the marker doesn't have a tail
setRange: (range, options={}) ->
@consolidateObserverNotifications false, =>
range = Range.fromObject(range)
@@ -24,22 +37,47 @@ class BufferMarker
@setTailPosition(range.start) unless options.noTail
@setHeadPosition(range.end)
# Public: Identifies if the ending position of a marker is greater than the starting position.
#
# This can happen when, for example, you highlight text "up" in a {Buffer}.
#
# Returns a {Boolean}.
isReversed: ->
@tailPosition? and @headPosition.isLessThan(@tailPosition)
# Public: Identifies if the marker's head position is equal to its tail.
#
# Returns a {Boolean}.
isRangeEmpty: ->
@getHeadPosition().isEqual(@getTailPosition())
# Public: Retrieves the {Range} between a marker's head and its tail.
#
# Returns a {Range}.
getRange: ->
if @tailPosition
new Range(@tailPosition, @headPosition)
else
new Range(@headPosition, @headPosition)
# Public: Retrieves the position of the marker's head.
#
# Returns a {Point}.
getHeadPosition: -> @headPosition
# Public: Retrieves the position of the marker's tail.
#
# Returns a {Point}.
getTailPosition: -> @tailPosition ? @getHeadPosition()
# Public: Sets the position of the marker's head.
#
# newHeadPosition - The new {Point} to place the head
# options - A hash with the following keys:
# :clip - if `true`, the point is [clipped]{Buffer.clipPosition}
# :bufferChanged - if `true`, indicates that the {Buffer} should trigger an event that it's changed
#
# Returns a {Point} representing the new head position.
setHeadPosition: (newHeadPosition, options={}) ->
oldHeadPosition = @getHeadPosition()
newHeadPosition = Point.fromObject(newHeadPosition)
@@ -50,6 +88,14 @@ class BufferMarker
@notifyObservers({oldHeadPosition, newHeadPosition, bufferChanged})
@headPosition
# Public: Sets the position of the marker's tail.
#
# newHeadPosition - The new {Point} to place the tail
# options - A hash with the following keys:
# :clip - if `true`, the point is [clipped]{Buffer.clipPosition}
# :bufferChanged - if `true`, indicates that the {Buffer} should trigger an event that it's changed
#
# Returns a {Point} representing the new tail position.
setTailPosition: (newTailPosition, options={}) ->
oldTailPosition = @getTailPosition()
newTailPosition = Point.fromObject(newTailPosition)
@@ -60,21 +106,52 @@ class BufferMarker
@notifyObservers({oldTailPosition, newTailPosition, bufferChanged})
@tailPosition
# Public: Retrieves the starting position of the marker.
#
# Returns a {Point}.
getStartPosition: ->
@getRange().start
# Public: Retrieves the ending position of the marker.
#
# Returns a {Point}.
getEndPosition: ->
@getRange().end
# Public: Sets the marker's tail to the same position as the marker's head.
#
# This only works if there isn't already a tail position.
#
# Returns a {Point} representing the new tail position.
placeTail: ->
@setTailPosition(@getHeadPosition()) unless @tailPosition
# Public: Removes the tail from the marker.
clearTail: ->
oldTailPosition = @getTailPosition()
@tailPosition = null
newTailPosition = @getTailPosition()
@notifyObservers({oldTailPosition, newTailPosition, bufferChanged: false})
# Public: Identifies if a {Point} is within the marker.
#
# Returns a {Boolean}.
containsPoint: (point) ->
@getRange().containsPoint(point)
# Public: Sets a callback to be fired whenever a marker is changed.
observe: (callback) ->
@on 'changed', callback
cancel: => @unobserve(callback)
# Public: Removes the fired callback whenever a marker changes.
unobserve: (callback) ->
@off 'changed', callback
###
# Internal #
###
tryToInvalidate: (changedRange) ->
betweenStartAndEnd = @getRange().containsRange(changedRange, exclusive: false)
containsStart = changedRange.containsPoint(@getStartPosition(), exclusive: true)
@@ -102,14 +179,13 @@ class BufferMarker
handleBufferChange: (bufferChange) ->
@consolidateObserverNotifications true, =>
@setHeadPosition(@updatePosition(@headPosition, bufferChange, false), clip: false, bufferChanged: true)
@setTailPosition(@updatePosition(@tailPosition, bufferChange, true), clip: false, bufferChanged: true) if @tailPosition
@setHeadPosition(@updatePosition(@headPosition, bufferChange, true), clip: false, bufferChanged: true)
@setTailPosition(@updatePosition(@tailPosition, bufferChange, false), clip: false, bufferChanged: true) if @tailPosition
updatePosition: (position, bufferChange, isFirstPoint) ->
updatePosition: (position, bufferChange, isHead) ->
{ oldRange, newRange } = bufferChange
return position if oldRange.containsPoint(position, exclusive: true)
return position if isFirstPoint and oldRange.start.isEqual(position)
return position if not isHead and oldRange.start.isEqual(position)
return position if position.isLessThan(oldRange.end)
newRow = newRange.end.row
@@ -123,16 +199,6 @@ class BufferMarker
[newRow, newColumn]
observe: (callback) ->
@on 'changed', callback
cancel: => @unobserve(callback)
unobserve: (callback) ->
@off 'changed', callback
containsPoint: (point) ->
@getRange().containsPoint(point)
notifyObservers: ({oldHeadPosition, newHeadPosition, oldTailPosition, newTailPosition, bufferChanged} = {}) ->
return if @suppressObserverNotification
+48 -11
Ver Arquivo
@@ -2,6 +2,8 @@ fsUtils = require 'fs-utils'
_ = require 'underscore'
EventEmitter = require 'event-emitter'
CSON = require 'cson'
fs = require 'fs'
async = require 'async'
configDirPath = fsUtils.absolute("~/.atom")
bundledPackagesDirPath = fsUtils.join(resourcePath, "src/packages")
@@ -11,16 +13,25 @@ vendoredThemesDirPath = fsUtils.join(resourcePath, "vendor/themes")
userThemesDirPath = fsUtils.join(configDirPath, "themes")
userPackagesDirPath = fsUtils.join(configDirPath, "packages")
# Public: Handles all of Atom's configuration details.
#
# This includes loading and setting default options, as well as reading from the
# user's configuration file.
module.exports =
class Config
configDirPath: configDirPath
themeDirPaths: [userThemesDirPath, bundledThemesDirPath, vendoredThemesDirPath]
packageDirPaths: [userPackagesDirPath, vendoredPackagesDirPath, bundledPackagesDirPath]
userPackagesDirPath: userPackagesDirPath
lessSearchPaths: [fsUtils.join(resourcePath, 'static'), fsUtils.join(resourcePath, 'vendor')]
defaultSettings: null
settings: null
configFileHasErrors: null
###
# Internal #
###
constructor: ->
@defaultSettings =
core: _.clone(require('root-view').configDefaults)
@@ -29,24 +40,29 @@ class Config
@configFilePath = fsUtils.resolve(configDirPath, 'config', ['json', 'cson'])
@configFilePath ?= fsUtils.join(configDirPath, 'config.cson')
initializeConfigDirectory: ->
initializeConfigDirectory: (done) ->
return if fsUtils.exists(@configDirPath)
fsUtils.makeDirectory(@configDirPath)
queue = async.queue ({sourcePath, destinationPath}, callback) =>
fsUtils.copy(sourcePath, destinationPath, callback)
queue.drain = done
templateConfigDirPath = fsUtils.resolve(window.resourcePath, 'dot-atom')
onConfigDirFile = (path) =>
relativePath = path.substring(templateConfigDirPath.length + 1)
configPath = fsUtils.join(@configDirPath, relativePath)
fsUtils.write(configPath, fsUtils.read(path))
fsUtils.traverseTreeSync(templateConfigDirPath, onConfigDirFile, (path) -> true)
onConfigDirFile = (sourcePath) =>
relativePath = sourcePath.substring(templateConfigDirPath.length + 1)
destinationPath = fsUtils.join(@configDirPath, relativePath)
queue.push({sourcePath, destinationPath})
fsUtils.traverseTree(templateConfigDirPath, onConfigDirFile, (path) -> true)
configThemeDirPath = fsUtils.join(@configDirPath, 'themes')
onThemeDirFile = (path) ->
relativePath = path.substring(bundledThemesDirPath.length + 1)
configPath = fsUtils.join(configThemeDirPath, relativePath)
fsUtils.write(configPath, fsUtils.read(path))
fsUtils.traverseTreeSync(bundledThemesDirPath, onThemeDirFile, (path) -> true)
onThemeDirFile = (sourcePath) ->
relativePath = sourcePath.substring(bundledThemesDirPath.length + 1)
destinationPath = fsUtils.join(configThemeDirPath, relativePath)
queue.push({sourcePath, destinationPath})
fsUtils.traverseTree(bundledThemesDirPath, onThemeDirFile, (path) -> true)
load: ->
@initializeConfigDirectory()
@@ -62,10 +78,24 @@ class Config
console.error "Failed to load user config '#{@configFilePath}'", e.message
console.error e.stack
# Public: Retrieves the setting for the given key.
#
# keyPath - The {String} name of the key to retrieve
#
# Returns the value from Atom's default settings, the user's configuration file,
# or `null` if the key doesn't exist in either.
get: (keyPath) ->
_.valueForKeyPath(@settings, keyPath) ?
_.valueForKeyPath(@defaultSettings, keyPath)
# Public: Sets the value for a configuration setting.
#
# This value is stored in Atom's internal configuration file.
#
# keyPath - The {String} name of the key
# value - The value of the setting
#
# Returns the `value`.
set: (keyPath, value) ->
_.setValueForKeyPath(@settings, keyPath, value)
@update()
@@ -81,6 +111,13 @@ class Config
_.extend hash, defaults
@update()
# Public: Establishes an event listener for a given key.
#
# Whenever the value of the key is changed, a callback is fired.
#
# keyPath - The {String} name of the key to watch
# callback - The {Function} that fires when the. It is given a single argument, `value`,
# which is the new value of `keyPath`.
observe: (keyPath, callback) ->
value = @get(keyPath)
previousValue = _.clone(value)
+2 -1
Ver Arquivo
@@ -3,10 +3,11 @@ Point = require 'point'
Range = require 'range'
_ = require 'underscore'
# Internal:
module.exports =
class CursorView extends View
@content: ->
@pre class: 'cursor idle', => @raw '&nbsp;'
@div class: 'cursor idle', => @raw '&nbsp;'
blinkPeriod: 800
editor: null
+138 -14
Ver Arquivo
@@ -3,6 +3,9 @@ Range = require 'range'
EventEmitter = require 'event-emitter'
_ = require 'underscore'
# Public: The `Cursor` class represents the little blinking line identifying where text can be inserted.
#
# Cursors have some metadata attached in the form of a {BufferMarker}.
module.exports =
class Cursor
screenPosition: null
@@ -11,6 +14,9 @@ class Cursor
visible: true
needsAutoscroll: null
###
# Internal #
###
constructor: ({@editSession, @marker}) ->
@updateVisibility()
@editSession.observeMarker @marker, (e) =>
@@ -39,20 +45,6 @@ class Cursor
@editSession.removeCursor(this)
@trigger 'destroyed'
setScreenPosition: (screenPosition, options={}) ->
@changePosition options, =>
@editSession.setMarkerHeadScreenPosition(@marker, screenPosition, options)
getScreenPosition: ->
@editSession.getMarkerHeadScreenPosition(@marker)
setBufferPosition: (bufferPosition, options={}) ->
@changePosition options, =>
@editSession.setMarkerHeadBufferPosition(@marker, bufferPosition, options)
getBufferPosition: ->
@editSession.getMarkerHeadBufferPosition(@marker)
changePosition: (options, fn) ->
@goalColumn = null
@clearSelection()
@@ -60,82 +52,161 @@ class Cursor
unless fn()
@trigger 'autoscrolled' if @needsAutoscroll
###
# Public #
###
# Public: Moves a cursor to a given screen position.
#
# screenPosition - An {Array} of two numbers: the screen row, and the screen column.
# options - An object with the following keys:
# :autoscroll - A {Boolean} which, if `true`, scrolls the {EditSession} to wherever the cursor moves to
#
setScreenPosition: (screenPosition, options={}) ->
@changePosition options, =>
@editSession.setMarkerHeadScreenPosition(@marker, screenPosition, options)
# Public: Gets the screen position of the cursor.
#
# Returns an {Array} of two numbers: the screen row, and the screen column.
getScreenPosition: ->
@editSession.getMarkerHeadScreenPosition(@marker)
# Public: Moves a cursor to a given buffer position.
#
# bufferPosition - An {Array} of two numbers: the buffer row, and the buffer column.
# options - An object with the following keys:
# :autoscroll - A {Boolean} which, if `true`, scrolls the {EditSession} to wherever the cursor moves to
#
setBufferPosition: (bufferPosition, options={}) ->
@changePosition options, =>
@editSession.setMarkerHeadBufferPosition(@marker, bufferPosition, options)
# Public: Gets the current buffer position.
#
# Returns an {Array} of two numbers: the buffer row, and the buffer column.
getBufferPosition: ->
@editSession.getMarkerHeadBufferPosition(@marker)
# Public: If the marker range is empty, the cursor is marked as being visible.
updateVisibility: ->
@setVisible(@editSession.isMarkerRangeEmpty(@marker))
# Public: Sets the visibility of the cursor.
#
# visible - A {Boolean} indicating whether the cursor should be visible
setVisible: (visible) ->
if @visible != visible
@visible = visible
@needsAutoscroll ?= true if @visible and @isLastCursor()
@trigger 'visibility-changed', @visible
# Public: Retrieves the visibility of the cursor.
#
# Returns a {Boolean}.
isVisible: -> @visible
# Public: Identifies what the cursor considers a "word" RegExp.
#
# Returns a {RegExp}.
wordRegExp: ->
nonWordCharacters = config.get("editor.nonWordCharacters")
new RegExp("^[\t ]*$|[^\\s#{_.escapeRegExp(nonWordCharacters)}]+|[#{_.escapeRegExp(nonWordCharacters)}]+", "g")
# Public: Identifies if this cursor is the last in the {EditSession}.
#
# Returns a {Boolean}.
isLastCursor: ->
this == @editSession.getCursor()
# Public: Identifies if the cursor is surrounded by whitespace.
#
# "Surrounded" here means that all characters before and after the cursor is whitespace.
#
# Returns a {Boolean}.
isSurroundedByWhitespace: ->
{row, column} = @getBufferPosition()
range = [[row, Math.min(0, column - 1)], [row, Math.max(0, column + 1)]]
/^\s+$/.test @editSession.getTextInBufferRange(range)
# Public: Removes the setting for auto-scroll.
clearAutoscroll: ->
@needsAutoscroll = null
# Public: Deselects whatever the cursor is selecting.
clearSelection: ->
if @selection
@selection.goalBufferRange = null
@selection.clear() unless @selection.retainSelection
# Public: Retrieves the cursor's screen row.
#
# Returns a {Number}.
getScreenRow: ->
@getScreenPosition().row
# Public: Retrieves the cursor's screen column.
#
# Returns a {Number}.
getScreenColumn: ->
@getScreenPosition().column
# Public: Retrieves the cursor's buffer row.
#
# Returns a {Number}.
getBufferRow: ->
@getBufferPosition().row
# Public: Retrieves the cursor's buffer column.
#
# Returns a {Number}.
getBufferColumn: ->
@getBufferPosition().column
# Public: Retrieves the cursor's buffer row text.
#
# Returns a {String}.
getCurrentBufferLine: ->
@editSession.lineForBufferRow(@getBufferRow())
# Public: Moves the cursor up one screen row.
moveUp: (rowCount = 1) ->
{ row, column } = @getScreenPosition()
column = @goalColumn if @goalColumn?
@setScreenPosition({row: row - rowCount, column: column})
@goalColumn = column
# Public: Moves the cursor down one screen row.
moveDown: (rowCount = 1) ->
{ row, column } = @getScreenPosition()
column = @goalColumn if @goalColumn?
@setScreenPosition({row: row + rowCount, column: column})
@goalColumn = column
# Public: Moves the cursor left one screen column.
moveLeft: ->
{ row, column } = @getScreenPosition()
[row, column] = if column > 0 then [row, column - 1] else [row - 1, Infinity]
@setScreenPosition({row, column})
# Public: Moves the cursor right one screen column.
moveRight: ->
{ row, column } = @getScreenPosition()
@setScreenPosition([row, column + 1], skipAtomicTokens: true, wrapBeyondNewlines: true, wrapAtSoftNewlines: true)
# Public: Moves the cursor to the top of the buffer.
moveToTop: ->
@setBufferPosition([0,0])
# Public: Moves the cursor to the bottom of the buffer.
moveToBottom: ->
@setBufferPosition(@editSession.getEofBufferPosition())
# Public: Moves the cursor to the beginning of the buffer line.
moveToBeginningOfLine: ->
@setBufferPosition([@getBufferRow(), 0])
# Public: Moves the cursor to the beginning of the first character in the line.
moveToFirstCharacterOfLine: ->
position = @getBufferPosition()
scanRange = @getCurrentLineBufferRange()
@@ -146,6 +217,7 @@ class Cursor
newPosition = [position.row, 0] if newPosition.isEqual(position)
@setBufferPosition(newPosition)
# Public: Moves the cursor to the beginning of the buffer line, skipping all whitespace.
skipLeadingWhitespace: ->
position = @getBufferPosition()
scanRange = @getCurrentLineBufferRange()
@@ -155,20 +227,30 @@ class Cursor
@setBufferPosition(endOfLeadingWhitespace) if endOfLeadingWhitespace.isGreaterThan(position)
# Public: Moves the cursor to the end of the buffer line.
moveToEndOfLine: ->
@setBufferPosition([@getBufferRow(), Infinity])
# Public: Moves the cursor to the beginning of the word.
moveToBeginningOfWord: ->
@setBufferPosition(@getBeginningOfCurrentWordBufferPosition())
# Public: Moves the cursor to the end of the word.
moveToEndOfWord: ->
if position = @getEndOfCurrentWordBufferPosition()
@setBufferPosition(position)
# Public: Moves the cursor to the beginning of the next word.
moveToBeginningOfNextWord: ->
if position = @getBeginningOfNextWordBufferPosition()
@setBufferPosition(position)
# Public: Retrieves the buffer position of where the current word starts.
#
# options - A hash with one option:
# :wordRegex - A {RegExp} indicating what constitutes a "word" (default: {wordRegExp})
#
# Returns a {Range}.
getBeginningOfCurrentWordBufferPosition: (options = {}) ->
allowPrevious = options.allowPrevious ? true
currentBufferPosition = @getBufferPosition()
@@ -184,6 +266,12 @@ class Cursor
beginningOfWordPosition or currentBufferPosition
# Public: Retrieves the buffer position of where the current word ends.
#
# options - A hash with one option:
# :wordRegex - A {RegExp} indicating what constitutes a "word" (default: {wordRegExp})
#
# Returns a {Range}.
getEndOfCurrentWordBufferPosition: (options = {}) ->
allowNext = options.allowNext ? true
currentBufferPosition = @getBufferPosition()
@@ -198,6 +286,12 @@ class Cursor
endOfWordPosition ? currentBufferPosition
# Public: Retrieves the buffer position of where the next word starts.
#
# options - A hash with one option:
# :wordRegex - A {RegExp} indicating what constitutes a "word" (default: {wordRegExp})
#
# Returns a {Range}.
getBeginningOfNextWordBufferPosition: (options = {}) ->
currentBufferPosition = @getBufferPosition()
start = if @isSurroundedByWhitespace() then currentBufferPosition else @getEndOfCurrentWordBufferPosition()
@@ -210,14 +304,29 @@ class Cursor
beginningOfNextWordPosition or currentBufferPosition
# Public: Gets the word located under the cursor.
#
# options - An object with properties based on {.getBeginningOfCurrentWordBufferPosition}.
#
# Returns a {String}.
getCurrentWordBufferRange: (options={}) ->
startOptions = _.extend(_.clone(options), allowPrevious: false)
endOptions = _.extend(_.clone(options), allowNext: false)
new Range(@getBeginningOfCurrentWordBufferPosition(startOptions), @getEndOfCurrentWordBufferPosition(endOptions))
# Public: Retrieves the range for the current line.
#
# options - A hash with the same keys as {EditSession.bufferRangeForBufferRow}
#
# Returns a {Range}.
getCurrentLineBufferRange: (options) ->
@editSession.bufferRangeForBufferRow(@getBufferRow(), options)
# Public: Retrieves the range for the current paragraph.
#
# A paragraph is defined as a block of text surrounded by empty lines.
#
# Returns a {Range}.
getCurrentParagraphBufferRange: ->
row = @getBufferRow()
return unless /\w/.test(@editSession.lineForBufferRow(row))
@@ -235,21 +344,36 @@ class Cursor
new Range([startRow, 0], [endRow, @editSession.lineLengthForBufferRow(endRow)])
# Public: Retrieves the characters that constitute a word preceeding the current cursor position.
#
# Returns a {String}.
getCurrentWordPrefix: ->
@editSession.getTextInBufferRange([@getBeginningOfCurrentWordBufferPosition(), @getBufferPosition()])
# Public: Identifies if the cursor is at the start of a line.
#
# Returns a {Boolean}.
isAtBeginningOfLine: ->
@getBufferPosition().column == 0
# Public: Retrieves the indentation level of the current line.
#
# Returns a {Number}.
getIndentLevel: ->
if @editSession.softTabs
@getBufferColumn() / @editSession.getTabLength()
else
@getBufferColumn()
# Public: Identifies if the cursor is at the end of a line.
#
# Returns a {Boolean}.
isAtEndOfLine: ->
@getBufferPosition().isEqual(@getCurrentLineBufferRange().end)
# Public: Retrieves the grammar's token scopes for the line.
#
# Returns an {Array} of {String}s.
getScopes: ->
@editSession.scopesForBufferPosition(@getBufferPosition())
+22
Ver Arquivo
@@ -5,17 +5,35 @@ pathWatcher = require 'pathwatcher'
File = require 'file'
EventEmitter = require 'event-emitter'
# Public: Represents a directory in the project.
#
# Directories contain an array of {File}s.
module.exports =
class Directory
path: null
# Public: Creates a new directory.
#
# path - A {String} representing the file directory
# symlink - A {Boolean} indicating if the path is a symlink (default: false)
constructor: (@path, @symlink=false) ->
# Public: Retrieves the basename of the directory.
#
# Returns a {String}.
getBaseName: ->
fsUtils.base(@path)
# Public: Retrieves the directory's path.
#
# Returns a {String}.
getPath: -> @path
# Public: Retrieves the file entries in the directory.
#
# This does follow symlinks.
#
# Returns an {Array} of {Files}.
getEntries: ->
directories = []
files = []
@@ -33,6 +51,10 @@ class Directory
directories.concat(files)
###
# Internal #
###
afterSubscribe: ->
@subscribeToNativeChangeEvents() if @subscriptionCount() == 1
+69 -3
Ver Arquivo
@@ -9,62 +9,128 @@ class DisplayBufferMarker
tailScreenPosition: null
valid: true
###
# Internal #
###
constructor: ({@id, @displayBuffer}) ->
@buffer = @displayBuffer.buffer
###
# Public #
###
# Public: Gets the screen range of the display marker.
#
# Returns a {Range}.
getScreenRange: ->
@displayBuffer.screenRangeForBufferRange(@getBufferRange(), wrapAtSoftNewlines: true)
# Public: Modifies the screen range of the display marker.
#
# screenRange - The new {Range} to use
# options - A hash of options matching those found in {BufferMarker.setRange}
setScreenRange: (screenRange, options) ->
@setBufferRange(@displayBuffer.bufferRangeForScreenRange(screenRange, options), options)
@setBufferRange(@displayBuffer.bufferRangeForScreenRange(screenRange), options)
# Public: Gets the buffer range of the display marker.
#
# Returns a {Range}.
getBufferRange: ->
@buffer.getMarkerRange(@id)
# Public: Modifies the buffer range of the display marker.
#
# screenRange - The new {Range} to use
# options - A hash of options matching those found in {BufferMarker.setRange}
setBufferRange: (bufferRange, options) ->
@buffer.setMarkerRange(@id, bufferRange, options)
# Public: Retrieves the screen position of the marker's head.
#
# Returns a {Point}.
getHeadScreenPosition: ->
@headScreenPosition ?= @displayBuffer.screenPositionForBufferPosition(@getHeadBufferPosition(), wrapAtSoftNewlines: true)
# Public: Sets the screen position of the marker's head.
#
# screenRange - The new {Point} to use
# options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition}
setHeadScreenPosition: (screenPosition, options) ->
screenPosition = @displayBuffer.clipScreenPosition(screenPosition, options)
@setHeadBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, options))
# Public: Retrieves the buffer position of the marker's head.
#
# Returns a {Point}.
getHeadBufferPosition: ->
@buffer.getMarkerHeadPosition(@id)
# Public: Sets the buffer position of the marker's head.
#
# screenRange - The new {Point} to use
# options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition}
setHeadBufferPosition: (bufferPosition) ->
@buffer.setMarkerHeadPosition(@id, bufferPosition)
# Public: Retrieves the screen position of the marker's tail.
#
# Returns a {Point}.
getTailScreenPosition: ->
@tailScreenPosition ?= @displayBuffer.screenPositionForBufferPosition(@getTailBufferPosition(), wrapAtSoftNewlines: true)
# Public: Sets the screen position of the marker's tail.
#
# screenRange - The new {Point} to use
# options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition}
setTailScreenPosition: (screenPosition, options) ->
screenPosition = @displayBuffer.clipScreenPosition(screenPosition, options)
@setTailBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, options))
# Public: Retrieves the buffer position of the marker's tail.
#
# Returns a {Point}.
getTailBufferPosition: ->
@buffer.getMarkerTailPosition(@id)
# Public: Sets the buffer position of the marker's tail.
#
# screenRange - The new {Point} to use
# options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition}
setTailBufferPosition: (bufferPosition) ->
@buffer.setMarkerTailPosition(@id, bufferPosition)
# Public: Sets the marker's tail to the same position as the marker's head.
#
# This only works if there isn't already a tail position.
#
# Returns a {Point} representing the new tail position.
placeTail: ->
@buffer.placeMarkerTail(@id)
# Public: Removes the tail from the marker.
clearTail: ->
@buffer.clearMarkerTail(@id)
# Public: Sets a callback to be fired whenever the marker is changed.
#
# callback - A {Function} to execute
observe: (callback) ->
@observeBufferMarkerIfNeeded()
@on 'changed', callback
cancel: => @unobserve(callback)
# Public: Removes the callback that's fired whenever the marker changes.
#
# callback - A {Function} to remove
unobserve: (callback) ->
@off 'changed', callback
@unobserveBufferMarkerIfNeeded()
###
# Internal #
###
observeBufferMarkerIfNeeded: ->
return if @subscriptionCount()
@getHeadScreenPosition() # memoize current value
+411 -79
Ver Arquivo
@@ -19,6 +19,10 @@ class DisplayBuffer
foldsById: null
markers: null
###
# Internal #
###
constructor: (@buffer, options={}) ->
@id = @constructor.idCounter++
@languageMode = options.languageMode
@@ -31,8 +35,6 @@ class DisplayBuffer
@tokenizedBuffer.on 'changed', @handleTokenizedBufferChange
@buffer.on 'markers-updated', @handleMarkersUpdated
setVisible: (visible) -> @tokenizedBuffer.setVisible(visible)
buildLineMap: ->
@lineMap = new LineMap
@lineMap.insertAtScreenRow 0, @buildLinesForBufferRows(0, @buffer.getLastRow())
@@ -44,6 +46,15 @@ class DisplayBuffer
@trigger 'changed', eventProperties
@resumeMarkerObservers()
###
# Public #
###
setVisible: (visible) -> @tokenizedBuffer.setVisible(visible)
# Public: Defines the limit at which the buffer begins to soft wrap text.
#
# softWrapColumn - A {Number} defining the soft wrap limit.
setSoftWrapColumn: (@softWrapColumn) ->
start = 0
end = @getLastRow()
@@ -52,18 +63,41 @@ class DisplayBuffer
bufferDelta = 0
@triggerChanged({ start, end, screenDelta, bufferDelta })
# Public: Gets the screen line for the given screen row.
#
# screenRow - A {Number} indicating the screen row.
#
# Returns a {ScreenLine}.
lineForRow: (row) ->
@lineMap.lineForScreenRow(row)
# Public: Gets the screen lines for the given screen row range.
#
# startRow - A {Number} indicating the beginning screen row.
# endRow - A {Number} indicating the ending screen row.
#
# Returns an {Array} of {ScreenLine}s.
linesForRows: (startRow, endRow) ->
@lineMap.linesForScreenRows(startRow, endRow)
# Public: Gets all the screen lines.
#
# Returns an {Array} of {ScreenLines}s.
getLines: ->
@lineMap.linesForScreenRows(0, @lineMap.lastScreenRow())
# Public: Given starting and ending screen rows, this returns an array of the
# buffer rows corresponding to every screen row in the range
#
# startRow - The screen row {Number} to start at
# endRow - The screen row {Number} to end at (default: the last screen row)
#
# Returns an {Array} of buffer rows as {Numbers}s.
bufferRowsForScreenRows: (startRow, endRow) ->
@lineMap.bufferRowsForScreenRows(startRow, endRow)
# Public: Folds all the foldable lines in the buffer.
foldAll: ->
for currentRow in [0..@buffer.getLastRow()]
[startRow, endRow] = @languageMode.rowRangeForFoldAtBufferRow(currentRow) ? []
@@ -71,6 +105,7 @@ class DisplayBuffer
@createFold(startRow, endRow)
# Public: Unfolds all the foldable lines in the buffer.
unfoldAll: ->
for row in [@buffer.getLastRow()..0]
@activeFolds[row]?.forEach (fold) => @destroyFold(fold)
@@ -90,6 +125,9 @@ class DisplayBuffer
endRow = currentRow
return [startRow, endRow] if startRow isnt endRow
# Public: Given a buffer row, this folds it.
#
# bufferRow - A {Number} indicating the buffer row
foldBufferRow: (bufferRow) ->
for currentRow in [bufferRow..0]
rowRange = @rowRangeForCommentAtBufferRow(currentRow)
@@ -103,9 +141,18 @@ class DisplayBuffer
return
# Public: Given a buffer row, this unfolds it.
#
# bufferRow - A {Number} indicating the buffer row
unfoldBufferRow: (bufferRow) ->
@largestFoldContainingBufferRow(bufferRow)?.destroy()
# Public: Creates a new fold between two row numbers.
#
# startRow - The row {Number} to start folding at
# endRow - The row {Number} to end the fold
#
# Returns the new {Fold}.
createFold: (startRow, endRow) ->
return fold if fold = @foldFor(startRow, endRow)
fold = new Fold(this, startRow, endRow)
@@ -127,15 +174,218 @@ class DisplayBuffer
fold
# Public: Given a {Fold}, determines if it is contained within another fold.
#
# fold - The {Fold} to check
#
# Returns the contaiing {Fold} (if it exists), `null` otherwise.
isFoldContainedByActiveFold: (fold) ->
for row, folds of @activeFolds
for otherFold in folds
return otherFold if fold != otherFold and fold.isContainedByFold(otherFold)
# Public: Given a starting and ending row, tries to find an existing fold.
#
# startRow - A {Number} representing a fold's starting row
# endRow - A {Number} representing a fold's ending row
#
# Returns a {Fold} (if it exists).
foldFor: (startRow, endRow) ->
_.find @activeFolds[startRow] ? [], (fold) ->
fold.startRow == startRow and fold.endRow == endRow
# Public: Removes any folds found that contain the given buffer row.
#
# bufferRow - The buffer row {Number} to check against
destroyFoldsContainingBufferRow: (bufferRow) ->
for row, folds of @activeFolds
for fold in new Array(folds...)
fold.destroy() if fold.getBufferRange().containsRow(bufferRow)
# Public: Given a buffer row, this returns the largest fold that starts there.
#
# Largest is defined as the fold whose difference between its start and end points
# are the greatest.
#
# bufferRow - A {Number} indicating the buffer row
#
# Returns a {Fold}.
largestFoldStartingAtBufferRow: (bufferRow) ->
return unless folds = @activeFolds[bufferRow]
(folds.sort (a, b) -> b.endRow - a.endRow)[0]
# Public: Given a screen row, this returns the largest fold that starts there.
#
# Largest is defined as the fold whose difference between its start and end points
# are the greatest.
#
# screenRow - A {Number} indicating the screen row
#
# Returns a {Fold}.
largestFoldStartingAtScreenRow: (screenRow) ->
@largestFoldStartingAtBufferRow(@bufferRowForScreenRow(screenRow))
# Public: Given a buffer row, this returns the largest fold that includes it.
#
# Largest is defined as the fold whose difference between its start and end points
# are the greatest.
#
# bufferRow - A {Number} indicating the buffer row
#
# Returns a {Fold}.
largestFoldContainingBufferRow: (bufferRow) ->
largestFold = null
for currentBufferRow in [bufferRow..0]
if fold = @largestFoldStartingAtBufferRow(currentBufferRow)
largestFold = fold if fold.endRow >= bufferRow
largestFold
# Public: Given a buffer range, this converts it into a screen range.
#
# bufferRange - A {Range} consisting of buffer positions
#
# Returns a {Range}.
screenLineRangeForBufferRange: (bufferRange) ->
@expandScreenRangeToLineEnds(
@lineMap.screenRangeForBufferRange(
@expandBufferRangeToLineEnds(bufferRange)))
# Public: Given a buffer row, this converts it into a screen row.
#
# bufferRow - A {Number} representing a buffer row
#
# Returns a {Number}.
screenRowForBufferRow: (bufferRow) ->
@lineMap.screenPositionForBufferPosition([bufferRow, 0]).row
lastScreenRowForBufferRow: (bufferRow) ->
@lineMap.screenPositionForBufferPosition([bufferRow, Infinity]).row
# Public: Given a screen row, this converts it into a buffer row.
#
# screenRow - A {Number} representing a screen row
#
# Returns a {Number}.
bufferRowForScreenRow: (screenRow) ->
@lineMap.bufferPositionForScreenPosition([screenRow, 0]).row
# Public: Given a buffer range, this converts it into a screen position.
#
# bufferRange - The {Range} to convert
#
# Returns a {Range}.
screenRangeForBufferRange: (bufferRange) ->
@lineMap.screenRangeForBufferRange(bufferRange)
# Public: Given a screen range, this converts it into a buffer position.
#
# screenRange - The {Range} to convert
#
# Returns a {Range}.
bufferRangeForScreenRange: (screenRange) ->
@lineMap.bufferRangeForScreenRange(screenRange)
# Public: Gets the number of lines in the buffer.
#
# Returns a {Number}.
lineCount: ->
@lineMap.screenLineCount()
# Public: Gets the number of the last row in the buffer.
#
# Returns a {Number}.
getLastRow: ->
@lineCount() - 1
# Public: Gets the length of the longest screen line.
#
# Returns a {Number}.
maxLineLength: ->
@lineMap.maxScreenLineLength
# Public: Given a buffer position, this converts it into a screen position.
#
# bufferPosition - An object that represents a buffer position. It can be either
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
# options - A hash of options with the following keys:
# :wrapBeyondNewlines -
# :wrapAtSoftNewlines -
#
# Returns a {Point}.
screenPositionForBufferPosition: (position, options) ->
@lineMap.screenPositionForBufferPosition(position, options)
# Public: Given a buffer range, this converts it into a screen position.
#
# screenPosition - An object that represents a buffer position. It can be either
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
# options - A hash of options with the following keys:
# :wrapBeyondNewlines -
# :wrapAtSoftNewlines -
#
# Returns a {Point}.
bufferPositionForScreenPosition: (position, options) ->
@lineMap.bufferPositionForScreenPosition(position, options)
# Public: Retrieves the grammar's token scopes for a buffer position.
#
# bufferPosition - A {Point} in the {Buffer}
#
# Returns an {Array} of {String}s.
scopesForBufferPosition: (bufferPosition) ->
@tokenizedBuffer.scopesForPosition(bufferPosition)
# Public: Retrieves the grammar's token for a buffer position.
#
# bufferPosition - A {Point} in the {Buffer}.
#
# Returns a {Token}.
tokenForBufferPosition: (bufferPosition) ->
@tokenizedBuffer.tokenForPosition(bufferPosition)
# Public: Retrieves the current tab length.
#
# Returns a {Number}.
getTabLength: ->
@tokenizedBuffer.getTabLength()
# Public: Specifies the tab length.
#
# tabLength - A {Number} that defines the new tab length.
setTabLength: (tabLength) ->
@tokenizedBuffer.setTabLength(tabLength)
# Public: Given a position, this clips it to a real position.
#
# For example, if `position`'s row exceeds the row count of the buffer,
# or if its column goes beyond a line's length, this "sanitizes" the value
# to a real position.
#
# position - The {Point} to clip
# options - A hash with the following values:
# :wrapBeyondNewlines - if `true`, continues wrapping past newlines
# :wrapAtSoftNewlines - if `true`, continues wrapping past soft newlines
# :screenLine - if `true`, indicates that you're using a line number, not a row number
#
# Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed.
clipScreenPosition: (position, options) ->
@lineMap.clipScreenPosition(position, options)
###
# Internal #
###
registerFold: (fold) ->
@activeFolds[fold.startRow] ?= []
@activeFolds[fold.startRow].push(fold)
@foldsById[fold.id] = fold
unregisterFold: (bufferRow, fold) ->
folds = @activeFolds[bufferRow]
_.remove(folds, fold)
delete @foldsById[fold.id]
delete @activeFolds[bufferRow] if folds.length == 0
destroyFold: (fold) ->
@unregisterFold(fold.startRow, fold)
@@ -154,83 +404,6 @@ class DisplayBuffer
@triggerChanged({ start, end, screenDelta, bufferDelta })
destroyFoldsContainingBufferRow: (bufferRow) ->
for row, folds of @activeFolds
for fold in new Array(folds...)
fold.destroy() if fold.getBufferRange().containsRow(bufferRow)
registerFold: (fold) ->
@activeFolds[fold.startRow] ?= []
@activeFolds[fold.startRow].push(fold)
@foldsById[fold.id] = fold
unregisterFold: (bufferRow, fold) ->
folds = @activeFolds[bufferRow]
_.remove(folds, fold)
delete @foldsById[fold.id]
delete @activeFolds[bufferRow] if folds.length == 0
largestFoldStartingAtBufferRow: (bufferRow) ->
return unless folds = @activeFolds[bufferRow]
(folds.sort (a, b) -> b.endRow - a.endRow)[0]
largestFoldStartingAtScreenRow: (screenRow) ->
@largestFoldStartingAtBufferRow(@bufferRowForScreenRow(screenRow))
largestFoldContainingBufferRow: (bufferRow) ->
largestFold = null
for currentBufferRow in [bufferRow..0]
if fold = @largestFoldStartingAtBufferRow(currentBufferRow)
largestFold = fold if fold.endRow >= bufferRow
largestFold
screenLineRangeForBufferRange: (bufferRange) ->
@expandScreenRangeToLineEnds(
@lineMap.screenRangeForBufferRange(
@expandBufferRangeToLineEnds(bufferRange)))
screenRowForBufferRow: (bufferRow) ->
@lineMap.screenPositionForBufferPosition([bufferRow, 0]).row
lastScreenRowForBufferRow: (bufferRow) ->
@lineMap.screenPositionForBufferPosition([bufferRow, Infinity]).row
bufferRowForScreenRow: (screenRow) ->
@lineMap.bufferPositionForScreenPosition([screenRow, 0]).row
screenRangeForBufferRange: (bufferRange) ->
@lineMap.screenRangeForBufferRange(bufferRange)
bufferRangeForScreenRange: (screenRange) ->
@lineMap.bufferRangeForScreenRange(screenRange)
lineCount: ->
@lineMap.screenLineCount()
getLastRow: ->
@lineCount() - 1
maxLineLength: ->
@lineMap.maxScreenLineLength
screenPositionForBufferPosition: (position, options) ->
@lineMap.screenPositionForBufferPosition(position, options)
bufferPositionForScreenPosition: (position, options) ->
@lineMap.bufferPositionForScreenPosition(position, options)
scopesForBufferPosition: (bufferPosition) ->
@tokenizedBuffer.scopesForPosition(bufferPosition)
getTabLength: ->
@tokenizedBuffer.getTabLength()
setTabLength: (tabLength) ->
@tokenizedBuffer.setTabLength(tabLength)
clipScreenPosition: (position, options) ->
@lineMap.clipScreenPosition(position, options)
handleBufferChange: (e) ->
allFolds = [] # Folds can modify @activeFolds, so first make sure we have a stable array of folds
allFolds.push(folds...) for row, folds of @activeFolds
@@ -299,6 +472,17 @@ class DisplayBuffer
lineFragments
###
# Public #
###
# Public: Given a line, finds the point where it would wrap.
#
# line - The {String} to check
# softWrapColumn - The {Number} where you want soft wrapping to occur
#
# Returns a {Number} representing the `line` position where the wrap would take place.
# Returns `null` if a wrap wouldn't occur.
findWrapColumn: (line, softWrapColumn) ->
return unless line.length > softWrapColumn
@@ -313,99 +497,247 @@ class DisplayBuffer
return column + 1 if /\s/.test(line[column])
return softWrapColumn
# Public: Given a range in screen coordinates, this expands it to the start and end of a line
#
# screenRange - The {Range} to expand
#
# Returns a new {Range}.
expandScreenRangeToLineEnds: (screenRange) ->
screenRange = Range.fromObject(screenRange)
{ start, end } = screenRange
new Range([start.row, 0], [end.row, @lineMap.lineForScreenRow(end.row).text.length])
# Public: Given a range in buffer coordinates, this expands it to the start and end of a line
#
# screenRange - The {Range} to expand
#
# Returns a new {Range}.
expandBufferRangeToLineEnds: (bufferRange) ->
bufferRange = Range.fromObject(bufferRange)
{ start, end } = bufferRange
new Range([start.row, 0], [end.row, Infinity])
# Public: Calculates a {Range} representing the start of the {Buffer} until the end.
#
# Returns a {Range}.
rangeForAllLines: ->
new Range([0, 0], @clipScreenPosition([Infinity, Infinity]))
# Public: Retrieves a {DisplayBufferMarker} based on its id.
#
# id - A {Number} representing a marker id
#
# Returns the {DisplayBufferMarker} (if it exists).
getMarker: (id) ->
@markers[id] ? new DisplayBufferMarker({id, displayBuffer: this})
# Public: Retrieves the active markers in the buffer.
#
# Returns an {Array} of existing {DisplayBufferMarker}s.
getMarkers: ->
_.values(@markers)
# Public: Constructs a new marker at the given screen range.
#
# range - The marker {Range} (representing the distance between the head and tail)
# options - Options to pass to the {BufferMarker} constructor
#
# Returns a {Number} representing the new marker's ID.
markScreenRange: (args...) ->
bufferRange = @bufferRangeForScreenRange(args.shift())
@markBufferRange(bufferRange, args...)
# Public: Constructs a new marker at the given buffer range.
#
# range - The marker {Range} (representing the distance between the head and tail)
# options - Options to pass to the {BufferMarker} constructor
#
# Returns a {Number} representing the new marker's ID.
markBufferRange: (args...) ->
@buffer.markRange(args...)
# Public: Constructs a new marker at the given screen position.
#
# range - The marker {Range} (representing the distance between the head and tail)
# options - Options to pass to the {BufferMarker} constructor
#
# Returns a {Number} representing the new marker's ID.
markScreenPosition: (screenPosition, options) ->
@markBufferPosition(@bufferPositionForScreenPosition(screenPosition), options)
# Public: Constructs a new marker at the given buffer position.
#
# range - The marker {Range} (representing the distance between the head and tail)
# options - Options to pass to the {BufferMarker} constructor
#
# Returns a {Number} representing the new marker's ID.
markBufferPosition: (bufferPosition, options) ->
@buffer.markPosition(bufferPosition, options)
# Public: Removes the marker with the given id.
#
# id - The {Number} of the ID to remove
destroyMarker: (id) ->
@buffer.destroyMarker(id)
delete @markers[id]
# Public: Gets the screen range of the display marker.
#
# id - The {Number} of the ID to check
#
# Returns a {Range}.
getMarkerScreenRange: (id) ->
@getMarker(id).getScreenRange()
# Public: Modifies the screen range of the display marker.
#
# id - The {Number} of the ID to change
# screenRange - The new {Range} to use
# options - A hash of options matching those found in {BufferMarker.setRange}
setMarkerScreenRange: (id, screenRange, options) ->
@getMarker(id).setScreenRange(screenRange, options)
# Public: Gets the buffer range of the display marker.
#
# id - The {Number} of the ID to check
#
# Returns a {Range}.
getMarkerBufferRange: (id) ->
@getMarker(id).getBufferRange()
# Public: Modifies the buffer range of the display marker.
#
# id - The {Number} of the ID to change
# screenRange - The new {Range} to use
# options - A hash of options matching those found in {BufferMarker.setRange}
setMarkerBufferRange: (id, bufferRange, options) ->
@getMarker(id).setBufferRange(bufferRange, options)
# Public: Retrieves the screen position of the marker's head.
#
# id - The {Number} of the ID to check
#
# Returns a {Point}.
getMarkerScreenPosition: (id) ->
@getMarkerHeadScreenPosition(id)
# Public: Retrieves the buffer position of the marker's head.
#
# id - The {Number} of the ID to check
#
# Returns a {Point}.
getMarkerBufferPosition: (id) ->
@getMarkerHeadBufferPosition(id)
# Public: Retrieves the screen position of the marker's head.
#
# id - The {Number} of the ID to check
#
# Returns a {Point}.
getMarkerHeadScreenPosition: (id) ->
@getMarker(id).getHeadScreenPosition()
# Public: Sets the screen position of the marker's head.
#
# id - The {Number} of the ID to change
# screenRange - The new {Point} to use
# options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition}
setMarkerHeadScreenPosition: (id, screenPosition, options) ->
@getMarker(id).setHeadScreenPosition(screenPosition, options)
# Public: Retrieves the buffer position of the marker's head.
#
# id - The {Number} of the ID to check
#
# Returns a {Point}.
getMarkerHeadBufferPosition: (id) ->
@getMarker(id).getHeadBufferPosition()
# Public: Sets the buffer position of the marker's head.
#
# id - The {Number} of the ID to check
# screenRange - The new {Point} to use
# options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition}
setMarkerHeadBufferPosition: (id, bufferPosition) ->
@getMarker(id).setHeadBufferPosition(bufferPosition)
# Public: Retrieves the screen position of the marker's tail.
#
# id - The {Number} of the ID to check
#
# Returns a {Point}.
getMarkerTailScreenPosition: (id) ->
@getMarker(id).getTailScreenPosition()
# Public: Sets the screen position of the marker's tail.
#
# id - The {Number} of the ID to change
# screenRange - The new {Point} to use
# options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition}
setMarkerTailScreenPosition: (id, screenPosition, options) ->
@getMarker(id).setTailScreenPosition(screenPosition, options)
# Public: Retrieves the buffer position of the marker's tail.
#
# id - The {Number} of the ID to check
#
# Returns a {Point}.
getMarkerTailBufferPosition: (id) ->
@getMarker(id).getTailBufferPosition()
# Public: Sets the buffer position of the marker's tail.
#
# id - The {Number} of the ID to check
# screenRange - The new {Point} to use
# options - A hash of options matching those found in {DisplayBuffer.bufferPositionForScreenPosition}
setMarkerTailBufferPosition: (id, bufferPosition) ->
@getMarker(id).setTailBufferPosition(bufferPosition)
# Public: Sets the marker's tail to the same position as the marker's head.
#
# This only works if there isn't already a tail position.
#
# id - A {Number} representing the marker to change
#
# Returns a {Point} representing the new tail position.
placeMarkerTail: (id) ->
@getMarker(id).placeTail()
# Public: Removes the tail from the marker.
#
# id - A {Number} representing the marker to change
clearMarkerTail: (id) ->
@getMarker(id).clearTail()
# Public: Identifies if the ending position of a marker is greater than the starting position.
#
# This can happen when, for example, you highlight text "up" in a {Buffer}.
#
# id - A {Number} representing the marker to check
#
# Returns a {Boolean}.
isMarkerReversed: (id) ->
@buffer.isMarkerReversed(id)
# Public: Identifies if the marker's head position is equal to its tail.
#
# id - A {Number} representing the marker to check
#
# Returns a {Boolean}.
isMarkerRangeEmpty: (id) ->
@buffer.isMarkerRangeEmpty(id)
# Public: Sets a callback to be fired whenever a marker is changed.
#
# id - A {Number} representing the marker to watch
# callback - A {Function} to execute
observeMarker: (id, callback) ->
@getMarker(id).observe(callback)
###
# Internal #
###
pauseMarkerObservers: ->
marker.pauseEvents() for marker in @getMarkers()
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+524 -61
Ver Arquivo
@@ -10,6 +10,9 @@ fsUtils = require 'fs-utils'
$ = require 'jquery'
_ = require 'underscore'
# Public: Represents the entire visual pane in Atom.
#
# The Editor manages the {EditSession}, which manages the file buffers.
module.exports =
class Editor extends View
@configDefaults:
@@ -23,6 +26,10 @@ class Editor extends View
@nextEditorId: 1
###
# Internal #
###
@content: (params) ->
@div class: @classes(params), tabindex: -1, =>
@subview 'gutter', new Gutter
@@ -56,6 +63,13 @@ class Editor extends View
newSelections: null
redrawOnReattach: false
# Public: The constructor for setting up an `Editor` instance.
#
# editSessionOrOptions - Either an {EditSession}, or an object with one property, `mini`.
# If `mini` is `true`, a "miniature" `EditSession` is constructed.
# Typically, this is ideal for scenarios where you need an Atom editor,
# but without all the chrome, like scrollbars, gutter, _e.t.c._.
#
initialize: (editSessionOrOptions) ->
if editSessionOrOptions instanceof EditSession
editSession = editSessionOrOptions
@@ -87,6 +101,9 @@ class Editor extends View
else
throw new Error("Must supply an EditSession or mini: true")
# Internal: Sets up the core Atom commands.
#
# Some commands are excluded from mini-editors.
bindKeys: ->
editorBindings =
'core:move-left': @moveCursorLeft
@@ -171,139 +188,418 @@ class Editor extends View
do (name, method) =>
@command name, (e) => method.call(this, e); false
getCursor: -> @activeEditSession.getCursor()
getCursors: -> @activeEditSession.getCursors()
addCursorAtScreenPosition: (screenPosition) -> @activeEditSession.addCursorAtScreenPosition(screenPosition)
addCursorAtBufferPosition: (bufferPosition) -> @activeEditSession.addCursorAtBufferPosition(bufferPosition)
moveCursorUp: -> @activeEditSession.moveCursorUp()
moveCursorDown: -> @activeEditSession.moveCursorDown()
moveCursorLeft: -> @activeEditSession.moveCursorLeft()
moveCursorRight: -> @activeEditSession.moveCursorRight()
moveCursorToBeginningOfWord: -> @activeEditSession.moveCursorToBeginningOfWord()
moveCursorToEndOfWord: -> @activeEditSession.moveCursorToEndOfWord()
moveCursorToBeginningOfNextWord: -> @activeEditSession.moveCursorToBeginningOfNextWord()
moveCursorToTop: -> @activeEditSession.moveCursorToTop()
moveCursorToBottom: -> @activeEditSession.moveCursorToBottom()
moveCursorToBeginningOfLine: -> @activeEditSession.moveCursorToBeginningOfLine()
moveCursorToFirstCharacterOfLine: -> @activeEditSession.moveCursorToFirstCharacterOfLine()
moveCursorToEndOfLine: -> @activeEditSession.moveCursorToEndOfLine()
moveLineUp: -> @activeEditSession.moveLineUp()
moveLineDown: -> @activeEditSession.moveLineDown()
setCursorScreenPosition: (position, options) -> @activeEditSession.setCursorScreenPosition(position, options)
duplicateLine: -> @activeEditSession.duplicateLine()
joinLine: -> @activeEditSession.joinLine()
getCursorScreenPosition: -> @activeEditSession.getCursorScreenPosition()
getCursorScreenRow: -> @activeEditSession.getCursorScreenRow()
setCursorBufferPosition: (position, options) -> @activeEditSession.setCursorBufferPosition(position, options)
getCursorBufferPosition: -> @activeEditSession.getCursorBufferPosition()
getCurrentParagraphBufferRange: -> @activeEditSession.getCurrentParagraphBufferRange()
getWordUnderCursor: (options) -> @activeEditSession.getWordUnderCursor(options)
###
# Public #
###
# Public: Retrieves a single cursor
#
# Returns a {Cursor}.
getCursor: -> @activeEditSession.getCursor()
# Public: Retrieves an array of all the cursors.
#
# Returns a {[Cursor]}.
getCursors: -> @activeEditSession.getCursors()
# Public: Adds a cursor at the provided `screenPosition`.
#
# screenPosition - An {Array} of two numbers: the screen row, and the screen column.
#
# Returns the new {Cursor}.
addCursorAtScreenPosition: (screenPosition) -> @activeEditSession.addCursorAtScreenPosition(screenPosition)
# Public: Adds a cursor at the provided `bufferPosition`.
#
# bufferPosition - An {Array} of two numbers: the buffer row, and the buffer column.
#
# Returns the new {Cursor}.
addCursorAtBufferPosition: (bufferPosition) -> @activeEditSession.addCursorAtBufferPosition(bufferPosition)
# Public: Moves every cursor up one row.
moveCursorUp: -> @activeEditSession.moveCursorUp()
# Public: Moves every cursor down one row.
moveCursorDown: -> @activeEditSession.moveCursorDown()
# Public: Moves every cursor left one column.
moveCursorLeft: -> @activeEditSession.moveCursorLeft()
# Public: Moves every cursor right one column.
moveCursorRight: -> @activeEditSession.moveCursorRight()
# Public: Moves every cursor to the beginning of the current word.
moveCursorToBeginningOfWord: -> @activeEditSession.moveCursorToBeginningOfWord()
# Public: Moves every cursor to the end of the current word.
moveCursorToEndOfWord: -> @activeEditSession.moveCursorToEndOfWord()
# Public: Moves the cursor to the beginning of the next word.
moveCursorToBeginningOfNextWord: -> @activeEditSession.moveCursorToBeginningOfNextWord()
# Public: Moves every cursor to the top of the buffer.
moveCursorToTop: -> @activeEditSession.moveCursorToTop()
# Public: Moves every cursor to the bottom of the buffer.
moveCursorToBottom: -> @activeEditSession.moveCursorToBottom()
# Public: Moves every cursor to the beginning of the line.
moveCursorToBeginningOfLine: -> @activeEditSession.moveCursorToBeginningOfLine()
# Public: Moves every cursor to the first non-whitespace character of the line.
moveCursorToFirstCharacterOfLine: -> @activeEditSession.moveCursorToFirstCharacterOfLine()
# Public: Moves every cursor to the end of the line.
moveCursorToEndOfLine: -> @activeEditSession.moveCursorToEndOfLine()
# Public: Moves the selected line up one row.
moveLineUp: -> @activeEditSession.moveLineUp()
# Public: Moves the selected line down one row.
moveLineDown: -> @activeEditSession.moveLineDown()
# Public: Sets the cursor based on a given screen position.
#
# position - An {Array} of two numbers: the screen row, and the screen column.
# options - An object with properties based on {Cursor.setScreenPosition}.
#
setCursorScreenPosition: (position, options) -> @activeEditSession.setCursorScreenPosition(position, options)
# Public: Duplicates the current line.
#
# If more than one cursor is present, only the most recently added one is considered.
duplicateLine: -> @activeEditSession.duplicateLine()
# Public: Joins the current line with the one below it.
#
# Multiple cursors are considered equally. If there's a selection in the editor,
# all the lines are joined together.
joinLine: -> @activeEditSession.joinLine()
# Public: Gets the current screen position.
#
# Returns an {Array} of two numbers: the screen row, and the screen column.
getCursorScreenPosition: -> @activeEditSession.getCursorScreenPosition()
# Public: Gets the current screen row.
#
# Returns a {Number}.
getCursorScreenRow: -> @activeEditSession.getCursorScreenRow()
# Public: Sets the cursor based on a given buffer position.
#
# position - An {Array} of two numbers: the buffer row, and the buffer column.
# options - An object with properties based on {Cursor.setBufferPosition}.
#
setCursorBufferPosition: (position, options) -> @activeEditSession.setCursorBufferPosition(position, options)
# Public: Gets the current buffer position of the cursor.
#
# Returns an {Array} of two numbers: the buffer row, and the buffer column.
getCursorBufferPosition: -> @activeEditSession.getCursorBufferPosition()
# Public: Retrieves the range for the current paragraph.
#
# A paragraph is defined as a block of text surrounded by empty lines.
#
# Returns a {Range}.
getCurrentParagraphBufferRange: -> @activeEditSession.getCurrentParagraphBufferRange()
# Public: Gets the word located under the cursor.
#
# options - An object with properties based on {Cursor.getBeginningOfCurrentWordBufferPosition}.
#
# Returns a {String}.
getWordUnderCursor: (options) -> @activeEditSession.getWordUnderCursor(options)
# Public: Gets the selection at the specified index.
#
# index - The id {Number} of the selection
#
# Returns a {Selection}.
getSelection: (index) -> @activeEditSession.getSelection(index)
# Public: Gets the last selection, _i.e._ the most recently added.
#
# Returns a {Selection}.
getSelections: -> @activeEditSession.getSelections()
# Public: Gets all selections, ordered by their position in the buffer.
#
# Returns an {Array} of {Selection}s.
getSelectionsOrderedByBufferPosition: -> @activeEditSession.getSelectionsOrderedByBufferPosition()
# Public: Gets the very last selection, as it's ordered in the buffer.
#
# Returns a {Selection}.
getLastSelectionInBuffer: -> @activeEditSession.getLastSelectionInBuffer()
# Public: Gets the currently selected text.
#
# Returns a {String}.
getSelectedText: -> @activeEditSession.getSelectedText()
# Public: Gets the buffer ranges of all the {Selection}s.
#
# This is ordered by their buffer position.
#
# Returns an {Array} of {Range}s.
getSelectedBufferRanges: -> @activeEditSession.getSelectedBufferRanges()
# Public: Gets the buffer range of the most recently added {Selection}.
#
# Returns a {Range}.
getSelectedBufferRange: -> @activeEditSession.getSelectedBufferRange()
# Public: Given a buffer range, this removes all previous selections and creates a new selection for it.
#
# bufferRange - A {Range} in the buffer
# options - A hash of options
setSelectedBufferRange: (bufferRange, options) -> @activeEditSession.setSelectedBufferRange(bufferRange, options)
# Public: Given an array of buffer ranges, this removes all previous selections and creates new selections for them.
#
# bufferRanges - An {Array} of {Range}s in the buffer
# options - A hash of options
setSelectedBufferRanges: (bufferRanges, options) -> @activeEditSession.setSelectedBufferRanges(bufferRanges, options)
# Public: Given a buffer range, this adds a new selection for it.
#
# bufferRange - A {Range} in the buffer
# options - A hash of options
#
# Returns the new {Selection}.
addSelectionForBufferRange: (bufferRange, options) -> @activeEditSession.addSelectionForBufferRange(bufferRange, options)
# Public: Selects the text one position right of the cursor.
selectRight: -> @activeEditSession.selectRight()
# Public: Selects the text one position left of the cursor.
selectLeft: -> @activeEditSession.selectLeft()
# Public: Selects all the text one position above the cursor.
selectUp: -> @activeEditSession.selectUp()
# Public: Selects all the text one position below the cursor.
selectDown: -> @activeEditSession.selectDown()
# Public: Selects all the text from the current cursor position to the top of the buffer.
selectToTop: -> @activeEditSession.selectToTop()
# Public: Selects all the text from the current cursor position to the bottom of the buffer.
selectToBottom: -> @activeEditSession.selectToBottom()
# Public: Selects all the text in the buffer.
selectAll: -> @activeEditSession.selectAll()
# Public: Selects all the text from the current cursor position to the beginning of the line.
selectToBeginningOfLine: -> @activeEditSession.selectToBeginningOfLine()
# Public: Selects all the text from the current cursor position to the end of the line.
selectToEndOfLine: -> @activeEditSession.selectToEndOfLine()
# Public: Moves the current selection down one row.
addSelectionBelow: -> @activeEditSession.addSelectionBelow()
# Public: Moves the current selection up one row.
addSelectionAbove: -> @activeEditSession.addSelectionAbove()
# Public: Selects all the text from the current cursor position to the beginning of the word.
selectToBeginningOfWord: -> @activeEditSession.selectToBeginningOfWord()
# Public: Selects all the text from the current cursor position to the end of the word.
selectToEndOfWord: -> @activeEditSession.selectToEndOfWord()
# Public: Selects all the text from the current cursor position to the beginning of the next word.
selectToBeginningOfNextWord: -> @activeEditSession.selectToBeginningOfNextWord()
# Public: Selects the current word.
selectWord: -> @activeEditSession.selectWord()
# Public: Selects the current line.
selectLine: -> @activeEditSession.selectLine()
# Public: Selects the text from the current cursor position to a given position.
#
# position - An instance of {Point}, with a given `row` and `column`.
selectToScreenPosition: (position) -> @activeEditSession.selectToScreenPosition(position)
# Public: Transposes the current text selections.
#
# This only works if there is more than one selection. Each selection is transferred
# to the position of the selection after it. The last selection is transferred to the
# position of the first.
transpose: -> @activeEditSession.transpose()
# Public: Turns the current selection into upper case.
upperCase: -> @activeEditSession.upperCase()
# Public: Turns the current selection into lower case.
lowerCase: -> @activeEditSession.lowerCase()
# Public: Clears every selection. TODO
clearSelections: -> @activeEditSession.clearSelections()
# Public: Performs a backspace, removing the character found behind the cursor position.
backspace: -> @activeEditSession.backspace()
# Public: Performs a backspace to the beginning of the current word, removing characters found there.
backspaceToBeginningOfWord: -> @activeEditSession.backspaceToBeginningOfWord()
# Public: Performs a backspace to the beginning of the current line, removing characters found there.
backspaceToBeginningOfLine: -> @activeEditSession.backspaceToBeginningOfLine()
# Public: Performs a delete, removing the character found ahead the cursor position.
delete: -> @activeEditSession.delete()
# Public: Performs a delete to the end of the current word, removing characters found there.
deleteToEndOfWord: -> @activeEditSession.deleteToEndOfWord()
# Public: Performs a delete to the end of the current line, removing characters found there.
deleteLine: -> @activeEditSession.deleteLine()
# Public: Performs a cut to the end of the current line.
#
# Characters are removed, but the text remains in the clipboard.
cutToEndOfLine: -> @activeEditSession.cutToEndOfLine()
# Public: Inserts text at the current cursor positions.
#
# text - A {String} representing the text to insert.
# options - A set of options equivalent to {Selection.insertText}.
insertText: (text, options) -> @activeEditSession.insertText(text, options)
# Public: Inserts a new line at the current cursor positions.
insertNewline: -> @activeEditSession.insertNewline()
# Internal:
consolidateSelections: (e) -> e.abortKeyBinding() unless @activeEditSession.consolidateSelections()
# Public: Inserts a new line below the current cursor positions.
insertNewlineBelow: -> @activeEditSession.insertNewlineBelow()
# Public: Inserts a new line above the current cursor positions.
insertNewlineAbove: -> @activeEditSession.insertNewlineAbove()
# Public: Indents the current line.
#
# options - A set of options equivalent to {Selection.indent}.
indent: (options) -> @activeEditSession.indent(options)
autoIndent: (options) -> @activeEditSession.autoIndentSelectedRows(options)
# Public: TODO
autoIndent: (options) -> @activeEditSession.autoIndentSelectedRows()
# Public: Indents the selected rows.
indentSelectedRows: -> @activeEditSession.indentSelectedRows()
# Public: Outdents the selected rows.
outdentSelectedRows: -> @activeEditSession.outdentSelectedRows()
# Public: Cuts the selected text.
cutSelection: -> @activeEditSession.cutSelectedText()
# Public: Copies the selected text.
copySelection: -> @activeEditSession.copySelectedText()
paste: -> @activeEditSession.pasteText()
# Public: Pastes the text in the clipboard.
#
# options - A set of options equivalent to {Selection.insertText}.
paste: (options) -> @activeEditSession.pasteText(options)
# Public: Undos the last {Buffer} change.
undo: -> @activeEditSession.undo()
# Public: Redos the last {Buffer} change.
redo: -> @activeEditSession.redo()
transact: (fn) -> @activeEditSession.transact(fn)
commit: -> @activeEditSession.commit()
abort: -> @activeEditSession.abort()
# Public: Creates a new fold between two row numbers.
#
# startRow - The row {Number} to start folding at
# endRow - The row {Number} to end the fold
#
# Returns the new {Fold}.
createFold: (startRow, endRow) -> @activeEditSession.createFold(startRow, endRow)
# Public: Folds the current row.
foldCurrentRow: -> @activeEditSession.foldCurrentRow()
# Public: Unfolds the current row.
unfoldCurrentRow: -> @activeEditSession.unfoldCurrentRow()
# Public: Folds all the rows.
foldAll: -> @activeEditSession.foldAll()
# Public: Unfolds all the rows.
unfoldAll: -> @activeEditSession.unfoldAll()
# Public: Folds the most recent selection.
foldSelection: -> @activeEditSession.foldSelection()
# Public: Given the id of a {Fold}, this removes it.
#
# foldId - The fold id {Number} to remove
destroyFold: (foldId) -> @activeEditSession.destroyFold(foldId)
# Public: Removes any {Fold}s found that contain the given buffer row.
#
# bufferRow - The buffer row {Number} to check against
destroyFoldsContainingBufferRow: (bufferRow) -> @activeEditSession.destroyFoldsContainingBufferRow(bufferRow)
# Public: Determines if the given screen row is folded.
#
# screenRow - A {Number} indicating the screen row.
#
# Returns `true` if the screen row is folded, `false` otherwise.
isFoldedAtScreenRow: (screenRow) -> @activeEditSession.isFoldedAtScreenRow(screenRow)
# Public: Determines if the given buffer row is folded.
#
# screenRow - A {Number} indicating the buffer row.
#
# Returns `true` if the buffer row is folded, `false` otherwise.
isFoldedAtBufferRow: (bufferRow) -> @activeEditSession.isFoldedAtBufferRow(bufferRow)
# Public: Determines if the given row that the cursor is at is folded.
#
# Returns `true` if the row is folded, `false` otherwise.
isFoldedAtCursorRow: -> @activeEditSession.isFoldedAtCursorRow()
# Public: Gets the line for the given screen row.
#
# screenRow - A {Number} indicating the screen row.
#
# Returns a {String}.
lineForScreenRow: (screenRow) -> @activeEditSession.lineForScreenRow(screenRow)
# Public: Gets the lines for the given screen row boundaries.
#
# start - A {Number} indicating the beginning screen row.
# end - A {Number} indicating the ending screen row.
#
# Returns an {Array} of {String}s.
linesForScreenRows: (start, end) -> @activeEditSession.linesForScreenRows(start, end)
# Public: Gets the number of screen rows.
#
# Returns a {Number}.
screenLineCount: -> @activeEditSession.screenLineCount()
# Public: Defines the limit at which the buffer begins to soft wrap text.
#
# softWrapColumn - A {Number} defining the soft wrap limit
setSoftWrapColumn: (softWrapColumn) ->
softWrapColumn ?= @calcSoftWrapColumn()
@activeEditSession.setSoftWrapColumn(softWrapColumn) if softWrapColumn
# Public: Gets the length of the longest screen line.
#
# Returns a {Number}.
maxScreenLineLength: -> @activeEditSession.maxScreenLineLength()
# Public: Gets the text in the last screen row.
#
# Returns a {String}.
getLastScreenRow: -> @activeEditSession.getLastScreenRow()
# Public: Given a position, this clips it to a real position.
#
# For example, if `position`'s row exceeds the row count of the buffer,
# or if its column goes beyond a line's length, this "sanitizes" the value
# to a real position.
#
# position - The {Point} to clip
# options - A hash with the following values:
# :wrapBeyondNewlines - if `true`, continues wrapping past newlines
# :wrapAtSoftNewlines - if `true`, continues wrapping past soft newlines
# :screenLine - if `true`, indicates that you're using a line number, not a row number
#
# Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed.
clipScreenPosition: (screenPosition, options={}) -> @activeEditSession.clipScreenPosition(screenPosition, options)
screenPositionForBufferPosition: (position, options) -> @activeEditSession.screenPositionForBufferPosition(position, options)
bufferPositionForScreenPosition: (position, options) -> @activeEditSession.bufferPositionForScreenPosition(position, options)
screenRangeForBufferRange: (range) -> @activeEditSession.screenRangeForBufferRange(range)
bufferRangeForScreenRange: (range) -> @activeEditSession.bufferRangeForScreenRange(range)
bufferRowsForScreenRows: (startRow, endRow) -> @activeEditSession.bufferRowsForScreenRows(startRow, endRow)
getLastScreenRow: -> @activeEditSession.getLastScreenRow()
# Public: Given a buffer position, this converts it into a screen position.
#
# bufferPosition - An object that represents a buffer position. It can be either
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
# options - The same options available to {DisplayBuffer.screenPositionForBufferPosition}.
#
# Returns a {Point}.
screenPositionForBufferPosition: (position, options) -> @activeEditSession.screenPositionForBufferPosition(position, options)
# Public: Given a buffer range, this converts it into a screen position.
#
# screenPosition - An object that represents a buffer position. It can be either
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
# options - The same options available to {DisplayBuffer.bufferPositionForScreenPosition}.
#
# Returns a {Point}.
bufferPositionForScreenPosition: (position, options) -> @activeEditSession.bufferPositionForScreenPosition(position, options)
# Public: Given a buffer range, this converts it into a screen position.
#
# bufferRange - The {Range} to convert
#
# Returns a {Range}.
screenRangeForBufferRange: (range) -> @activeEditSession.screenRangeForBufferRange(range)
# Public: Given a screen range, this converts it into a buffer position.
#
# screenRange - The {Range} to convert
#
# Returns a {Range}.
bufferRangeForScreenRange: (range) -> @activeEditSession.bufferRangeForScreenRange(range)
# Public: Given a starting and ending row, this converts every row into a buffer position.
#
# startRow - The row {Number} to start at
# endRow - The row {Number} to end at (default: {.getLastScreenRow})
#
# Returns an {Array} of {Range}s.
bufferRowsForScreenRows: (startRow, endRow) -> @activeEditSession.bufferRowsForScreenRows(startRow, endRow)
# Public: Gets the number of the last row in the buffer.
#
# Returns a {Number}.
getLastScreenRow: -> @activeEditSession.getLastScreenRow()
# Internal:
logCursorScope: ->
console.log @activeEditSession.getCursorScopes()
# Public: Emulates the "page down" key, where the last row of a buffer scrolls to become the first.
pageDown: ->
newScrollTop = @scrollTop() + @scrollView[0].clientHeight
@activeEditSession.moveCursorDown(@getPageRows())
@scrollTop(newScrollTop, adjustVerticalScrollbar: true)
# Public: Emulates the "page up" key, where the frst row of a buffer scrolls to become the last.
pageUp: ->
newScrollTop = @scrollTop() - @scrollView[0].clientHeight
@activeEditSession.moveCursorUp(@getPageRows())
@scrollTop(newScrollTop, adjustVerticalScrollbar: true)
# Public: Gets the number of actual page rows existing in an editor.
#
# Returns a {Number}.
getPageRows: ->
Math.max(1, Math.ceil(@scrollView[0].clientHeight / @lineHeight))
# Public: Set whether invisible characters are shown.
#
# showInvisibles - A {Boolean} which, if `true`, show invisible characters
setShowInvisibles: (showInvisibles) ->
return if showInvisibles == @showInvisibles
@showInvisibles = showInvisibles
@resetDisplay()
# Public: Defines which characters are invisible.
#
# invisibles - A hash defining the invisible characters: The defaults are:
# :eol - `\u00ac`
# :space - `\u00b7`
# :tab - `\u00bb`
# :cr - `\u00a4`
setInvisibles: (@invisibles={}) ->
_.defaults @invisibles,
eol: '\u00ac'
@@ -312,25 +608,83 @@ class Editor extends View
cr: '\u00a4'
@resetDisplay()
# Public: Sets whether you want to show the indentation guides.
#
# showIndentGuide - A {Boolean} you can set to `true` if you want to see the indentation guides.
setShowIndentGuide: (showIndentGuide) ->
return if showIndentGuide == @showIndentGuide
@showIndentGuide = showIndentGuide
@resetDisplay()
# Public: Checks out the current HEAD revision of the file.
checkoutHead: -> @getBuffer().checkoutHead()
setText: (text) -> @getBuffer().setText(text)
getText: -> @getBuffer().getText()
# Public: Replaces the current buffer contents.
#
# text - A {String} containing the new buffer contents.
setText: (text) -> @activeEditSession.setText(text)
# Public: Retrieves the current buffer contents.
#
# Returns a {String}.
getText: -> @activeEditSession.getText()
# Public: Retrieves the current buffer's file path.
#
# Returns a {String}.
getPath: -> @activeEditSession?.getPath()
# Public: Gets the number of lines in a file.
#
# Returns a {Number}.
getLineCount: -> @getBuffer().getLineCount()
# Public: Gets the row number of the last line.
#
# Returns a {Number}.
getLastBufferRow: -> @getBuffer().getLastRow()
# Public: Given a range, returns the lines of text within it.
#
# range - A {Range} object specifying your points of interest
#
# Returns a {String} of the combined lines.
getTextInRange: (range) -> @getBuffer().getTextInRange(range)
# Public: Finds the last point in the current buffer.
#
# Returns a {Point} representing the last position.
getEofPosition: -> @getBuffer().getEofPosition()
# Public: Given a row, returns the line of text.
#
# row - A {Number} indicating the row.
#
# Returns a {String}.
lineForBufferRow: (row) -> @getBuffer().lineForRow(row)
# Public: Given a row, returns the length of the line of text.
#
# row - A {Number} indicating the row
#
# Returns a {Number}.
lineLengthForBufferRow: (row) -> @getBuffer().lineLengthForRow(row)
# Public: Given a buffer row, this retrieves the range for that line.
#
# row - A {Number} identifying the row
# options - A hash with one key, `includeNewline`, which specifies whether you
# want to include the trailing newline
#
# Returns a {Range}.
rangeForBufferRow: (row) -> @getBuffer().rangeForRow(row)
# Public: Scans for text in the buffer, calling a function on each match.
#
# regex - A {RegExp} representing the text to find
# range - A {Range} in the buffer to search within
# iterator - A {Function} that's called on each match
scanInBufferRange: (args...) -> @getBuffer().scanInRange(args...)
# Public: Scans for text in the buffer _backwards_, calling a function on each match.
#
# regex - A {RegExp} representing the text to find
# range - A {Range} in the buffer to search within
# iterator - A {Function} that's called on each match
backwardsScanInBufferRange: (args...) -> @getBuffer().backwardsScanInRange(args...)
###
# Internal #
###
configure: ->
@observeConfig 'editor.showLineNumbers', (showLineNumbers) => @gutter.setShowLineNumbers(showLineNumbers)
@observeConfig 'editor.showInvisibles', (showInvisibles) => @setShowInvisibles(showInvisibles)
@@ -404,10 +758,6 @@ class Editor extends View
@verticalScrollbar.on 'scroll', =>
@scrollTop(@verticalScrollbar.scrollTop(), adjustVerticalScrollbar: false)
unless @mini
@gutter.widthChanged = (newWidth) =>
@scrollView.css('left', newWidth + 'px')
@scrollView.on 'scroll', =>
if @scrollView.scrollLeft() == 0
@gutter.removeClass('drop-shadow')
@@ -438,7 +788,6 @@ class Editor extends View
return if @attached
@attached = true
@calculateDimensions()
@hiddenInput.width(@charWidth)
@setSoftWrapColumn() if @activeEditSession.getSoftWrap()
@subscribe $(window), "resize.editor-#{@id}", => @requestDisplayUpdate()
@focus() if @isFocused
@@ -477,6 +826,14 @@ class Editor extends View
@activeEditSession.on "grammar-changed.editor", =>
@trigger 'editor:grammar-changed'
@activeEditSession.on 'selection-added.editor', (selection) =>
@newCursors.push(selection.cursor)
@newSelections.push(selection)
@requestDisplayUpdate()
@activeEditSession.on 'screen-lines-changed.editor', (e) =>
@handleScreenLinesChange(e)
@trigger 'editor:path-changed'
@resetDisplay()
@@ -489,8 +846,6 @@ class Editor extends View
setModel: (editSession) ->
@edit(editSession)
getBuffer: -> @activeEditSession.buffer
showBufferConflictAlert: (editSession) ->
atom.confirm(
editSession.getPath(),
@@ -521,23 +876,53 @@ class Editor extends View
else
@scrollTop() + @scrollView.height()
###
# Public #
###
# Public: Retrieves the {EditSession}'s buffer.
#
# Returns the current {Buffer}.
getBuffer: -> @activeEditSession.buffer
# Public: Scrolls the editor to the bottom.
scrollToBottom: ->
@scrollBottom(@screenLineCount() * @lineHeight)
# Public: Scrolls the editor to the position of the most recently added cursor.
#
# The editor is also centered.
scrollToCursorPosition: ->
@scrollToBufferPosition(@getCursorBufferPosition(), center: true)
# Public: Scrolls the editor to the given buffer position.
#
# bufferPosition - An object that represents a buffer position. It can be either
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
# options - A hash matching the options available to {.scrollToPixelPosition}
scrollToBufferPosition: (bufferPosition, options) ->
@scrollToPixelPosition(@pixelPositionForBufferPosition(bufferPosition), options)
# Public: Scrolls the editor to the given screen position.
#
# screenPosition - An object that represents a buffer position. It can be either
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
# options - A hash matching the options available to {.scrollToPixelPosition}
scrollToScreenPosition: (screenPosition, options) ->
@scrollToPixelPosition(@pixelPositionForScreenPosition(screenPosition), options)
# Public: Scrolls the editor to the given pixel position.
#
# pixelPosition - An object that represents a pixel position. It can be either
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
# options - A hash with the following keys:
# :center - if `true`, the position is scrolled such that it's in the center of the editor
scrollToPixelPosition: (pixelPosition, options) ->
return unless @attached
@scrollVertically(pixelPosition, options)
@scrollHorizontally(pixelPosition)
# Internal: Scrolls the editor vertically to a given position.
scrollVertically: (pixelPosition, {center}={}) ->
scrollViewHeight = @scrollView.height()
scrollTop = @scrollTop()
@@ -558,6 +943,7 @@ class Editor extends View
else if desiredTop < scrollTop
@scrollTop(desiredTop)
# Internal: Scrolls the editor horizontally to a given position.
scrollHorizontally: (pixelPosition) ->
return if @activeEditSession.getSoftWrap()
@@ -573,6 +959,11 @@ class Editor extends View
else if desiredLeft < @scrollView.scrollLeft()
@scrollView.scrollLeft(desiredLeft)
# Public: Given a buffer range, this highlights all the folds within that range
#
# "Highlighting" essentially just adds the `selected` class to the line
#
# bufferRange - The {Range} to check
highlightFoldsContainingBufferRange: (bufferRange) ->
screenLines = @linesForScreenRows(@firstRenderedScreenRow, @lastRenderedScreenRow)
for screenLine, i in screenLines
@@ -593,9 +984,11 @@ class Editor extends View
@activeEditSession.setScrollTop(@scrollTop())
@activeEditSession.setScrollLeft(@scrollView.scrollLeft())
# Public: Activates soft tabs in the editor.
toggleSoftTabs: ->
@activeEditSession.setSoftTabs(not @activeEditSession.softTabs)
# Public: Activates soft wraps in the editor.
toggleSoftWrap: ->
@setSoftWrap(not @activeEditSession.getSoftWrap())
@@ -605,6 +998,11 @@ class Editor extends View
else
Infinity
# Public: Sets the soft wrap column for the editor.
#
# softWrap - A {Boolean} which, if `true`, sets soft wraps
# softWrapColumn - A {Number} indicating the length of a line in the editor when soft
# wrapping turns on
setSoftWrap: (softWrap, softWrapColumn=undefined) ->
@activeEditSession.setSoftWrap(softWrap)
@setSoftWrapColumn(softWrapColumn) if @attached
@@ -617,6 +1015,9 @@ class Editor extends View
@removeClass 'soft-wrap'
$(window).off 'resize', @_setSoftWrapColumn
# Public: Sets the font size for the editor.
#
# fontSize - A {Number} indicating the font size in pixels.
setFontSize: (fontSize) ->
headTag = $("head")
styleTag = headTag.find("style.font-size")
@@ -631,9 +1032,15 @@ class Editor extends View
else
@redrawOnReattach = @attached
# Public: Retrieves the font size for the editor.
#
# Returns a {Number} indicating the font size in pixels.
getFontSize: ->
parseInt(@css("font-size"))
# Public: Sets the font family for the editor.
#
# fontFamily - A {String} identifying the CSS `font-family`,
setFontFamily: (fontFamily) ->
return if fontFamily == undefined
headTag = $("head")
@@ -645,11 +1052,16 @@ class Editor extends View
styleTag.text(".editor {font-family: #{fontFamily}}")
@redraw()
# Public: Gets the font family for the editor.
#
# Returns a {String} identifying the CSS `font-family`,
getFontFamily: -> @css("font-family")
# Public: Clears the CSS `font-family` property from the editor.
clearFontFamily: ->
$('head style.editor-font-family').remove()
# Public: Clears the CSS `font-family` property from the editor.
redraw: ->
return unless @hasParent()
return unless @attached
@@ -671,6 +1083,9 @@ class Editor extends View
splitDown: (items...) ->
@getPane()?.splitDown(items...).activeView
# Public: Retrieve's the `Editor`'s pane.
#
# Returns a {Pane}.
getPane: ->
@parent('.item-views').parent('.pane').view()
@@ -725,8 +1140,12 @@ class Editor extends View
appendToLinesView: (view) ->
@overlayer.append(view)
###
# Internal #
###
calculateDimensions: ->
fragment = $('<pre class="line" style="position: absolute; visibility: hidden;"><span>x</span></div>')
fragment = $('<div class="line" style="position: absolute; visibility: hidden;"><span>x</span></div>')
@renderedLines.append(fragment)
lineRect = fragment[0].getBoundingClientRect()
@@ -734,7 +1153,6 @@ class Editor extends View
@lineHeight = lineRect.height
@charWidth = charRect.width
@charHeight = charRect.height
@height(@lineHeight) if @mini
fragment.remove()
updateLayerDimensions: ->
@@ -768,14 +1186,6 @@ class Editor extends View
@removeAllCursorAndSelectionViews()
@updateLayerDimensions()
@setScrollPositionFromActiveEditSession()
@activeEditSession.on 'selection-added.editor', (selection) =>
@newCursors.push(selection.cursor)
@newSelections.push(selection)
@requestDisplayUpdate()
@activeEditSession.on 'screen-lines-changed.editor', (e) => @handleScreenLinesChange(e)
@newCursors = @activeEditSession.getCursors()
@newSelections = @activeEditSession.getSelections()
@updateDisplay(suppressAutoScroll: true)
@@ -975,15 +1385,34 @@ class Editor extends View
@renderedLines.css('padding-bottom', paddingBottom)
@gutter.lineNumbers.css('padding-bottom', paddingBottom)
###
# Public #
###
# Public: Retrieves the number of the row that is visible and currently at the top of the editor.
#
# Returns a {Number}.
getFirstVisibleScreenRow: ->
Math.floor(@scrollTop() / @lineHeight)
# Public: Retrieves the number of the row that is visible and currently at the top of the editor.
#
# Returns a {Number}.
getLastVisibleScreenRow: ->
Math.max(0, Math.ceil((@scrollTop() + @scrollView.height()) / @lineHeight) - 1)
# Public: Given a row number, identifies if it is currently visible.
#
# row - A row {Number} to check
#
# Returns a {Boolean}.
isScreenRowVisible: (row) ->
@getFirstVisibleScreenRow() <= row <= @getLastVisibleScreenRow()
###
# Internal #
###
handleScreenLinesChange: (change) ->
@pendingChanges.push(change)
@requestDisplayUpdate()
@@ -1052,7 +1481,7 @@ class Editor extends View
attributePairs = []
attributePairs.push "#{attributeName}=\"#{value}\"" for attributeName, value of lineAttributes
line.push("<pre #{attributePairs.join(' ')}>")
line.push("<div #{attributePairs.join(' ')}>")
invisibles = @invisibles if @showInvisibles
@@ -1081,7 +1510,7 @@ class Editor extends View
line.push("<span class='fold-marker'/>") if fold
line.push('</pre>')
line.push('</div>')
line.join('')
lineElementForScreenRow: (screenRow) ->
@@ -1090,9 +1519,25 @@ class Editor extends View
toggleLineCommentsInSelection: ->
@activeEditSession.toggleLineCommentsInSelection()
###
# Public #
###
# Public: Converts a buffer position to a pixel position.
#
# position - An object that represents a buffer position. It can be either
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
#
# Returns an object with two values: `top` and `left`, representing the pixel positions.
pixelPositionForBufferPosition: (position) ->
@pixelPositionForScreenPosition(@screenPositionForBufferPosition(position))
# Public: Converts a screen position to a pixel position.
#
# position - An object that represents a screen position. It can be either
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
#
# Returns an object with two values: `top` and `left`, representing the pixel positions.
pixelPositionForScreenPosition: (position) ->
return { top: 0, left: 0 } unless @isOnDom() and @isVisible()
{row, column} = Point.fromObject(position)
@@ -1158,6 +1603,7 @@ class Editor extends View
new Point(row, column)
# Public: Highlights the current line the cursor is on.
highlightCursorLine: ->
return if @mini
@@ -1168,13 +1614,20 @@ class Editor extends View
else
@highlightedLine = null
# Public: Retrieves the current {EditSession}'s grammar.
#
# Returns a {String} indicating the language's grammar rules.
getGrammar: ->
@activeEditSession.getGrammar()
# Public: Sets the current {EditSession}'s grammar. This only works for mini-editors.
#
# grammar - A {String} indicating the language's grammar rules.
setGrammar: (grammar) ->
throw new Error("Only mini-editors can explicity set their grammar") unless @mini
@activeEditSession.setGrammar(grammar)
# Public: Reloads the current grammar.
reloadGrammar: ->
@activeEditSession.reloadGrammar()
@@ -1185,6 +1638,7 @@ class Editor extends View
@on event, =>
callback(this, event)
# Internal: Replaces all the currently selected text.
replaceSelectedText: (replaceFn) ->
selection = @getSelection()
return false if selection.isEmpty()
@@ -1195,10 +1649,19 @@ class Editor extends View
@insertText(text, select: true)
true
# Public: Copies the current file path to the native clipboard.
copyPathToPasteboard: ->
path = @getPath()
pasteboard.write(path) if path?
###
# Internal #
###
transact: (fn) -> @activeEditSession.transact(fn)
commit: -> @activeEditSession.commit()
abort: -> @activeEditSession.abort()
saveDebugSnapshot: ->
atom.showSaveDialog (path) =>
fsUtils.write(path, @getDebugSnapshot()) if path
+64 -36
Ver Arquivo
@@ -1,22 +1,36 @@
_ = require 'underscore'
# Public: Provides a list of functions that can be used in Atom for event management.
#
# Each event can have more than one handler; that is, an event can trigger multiple functions.
module.exports =
on: (eventName, handler) ->
[eventName, namespace] = eventName.split('.')
# Public: Associates an event name with a function to perform.
#
# This is called endlessly, until the event is turned {.off}.
#
# eventNames - A {String} containing one or more space-separated events.
# handler - A {Function} that's executed when the event is triggered.
on: (eventNames, handler) ->
for eventName in eventNames.split(/\s+/) when eventName isnt ''
[eventName, namespace] = eventName.split('.')
@eventHandlersByEventName ?= {}
@eventHandlersByEventName[eventName] ?= []
@eventHandlersByEventName[eventName].push(handler)
@eventHandlersByEventName ?= {}
@eventHandlersByEventName[eventName] ?= []
@eventHandlersByEventName[eventName].push(handler)
if namespace
@eventHandlersByNamespace ?= {}
@eventHandlersByNamespace[namespace] ?= {}
@eventHandlersByNamespace[namespace][eventName] ?= []
@eventHandlersByNamespace[namespace][eventName].push(handler)
if namespace
@eventHandlersByNamespace ?= {}
@eventHandlersByNamespace[namespace] ?= {}
@eventHandlersByNamespace[namespace][eventName] ?= []
@eventHandlersByNamespace[namespace][eventName].push(handler)
@afterSubscribe?()
# Public: Associates an event name with a function to perform only once.
#
# eventName - A {String} name identifying an event
# handler - A {Function} that's executed when the event is triggered
one: (eventName, handler) ->
oneShotHandler = (args...) =>
@off(eventName, oneShotHandler)
@@ -24,6 +38,10 @@ module.exports =
@on eventName, oneShotHandler
# Public: Triggers a registered event.
#
# eventName - A {String} name identifying an event
# args - Any additional arguments to pass over to the event `handler`
trigger: (eventName, args...) ->
if @queuedEvents
@queuedEvents.push [eventName, args...]
@@ -37,47 +55,57 @@ module.exports =
if handlers = @eventHandlersByEventName?[eventName]
handlers.forEach (handler) -> handler(args...)
off: (eventName='', handler) ->
[eventName, namespace] = eventName.split('.')
eventName = undefined if eventName == ''
# Public: Stops executing handlers for a registered event.
#
# eventNames - A {String} containing one or more space-separated events.
# handler - The {Function} to remove from the event. If not provided, all handlers are removed.
off: (eventNames, handler) ->
if eventNames
for eventName in eventNames.split(/\s+/) when eventName isnt ''
[eventName, namespace] = eventName.split('.')
eventName = undefined if eventName == ''
subscriptionCountBefore = @subscriptionCount()
if !eventName? and !namespace?
if namespace
if eventName
handlers = @eventHandlersByNamespace?[namespace]?[eventName] ? []
for handler in new Array(handlers...)
_.remove(handlers, handler)
@off eventName, handler
else
for eventName, handlers of @eventHandlersByNamespace?[namespace] ? {}
for handler in new Array(handlers...)
_.remove(handlers, handler)
@off eventName, handler
else
subscriptionCountBefore = @subscriptionCount()
if handler
eventHandlers = @eventHandlersByEventName[eventName]
_.remove(eventHandlers, handler) if eventHandlers
else
delete @eventHandlersByEventName?[eventName]
@afterUnsubscribe?() if @subscriptionCount() < subscriptionCountBefore
else
subscriptionCountBefore = @subscriptionCount()
@eventHandlersByEventName = {}
@eventHandlersByNamespace = {}
else if namespace
if eventName
handlers = @eventHandlersByNamespace?[namespace]?[eventName] ? []
for handler in new Array(handlers...)
_.remove(handlers, handler)
@off eventName, handler
return
else
for eventName, handlers of @eventHandlersByNamespace?[namespace] ? {}
for handler in new Array(handlers...)
_.remove(handlers, handler)
@off eventName, handler
return
else
if handler
_.remove(@eventHandlersByEventName[eventName], handler)
else
delete @eventHandlersByEventName?[eventName]
@afterUnsubscribe?() if @subscriptionCount() < subscriptionCountBefore
@afterUnsubscribe?() if @subscriptionCount() < subscriptionCountBefore
# Public: When called, stops triggering any events.
pauseEvents: ->
@pauseCount ?= 0
if @pauseCount++ == 0
@queuedEvents ?= []
# Public: When called, resumes triggering events.
resumeEvents: ->
if --@pauseCount == 0
queuedEvents = @queuedEvents
@queuedEvents = null
@trigger(event...) for event in queuedEvents
# Public: Identifies how many events are registered.
#
# Returns a {Number}.
subscriptionCount: ->
count = 0
for name, handlers of @eventHandlersByEventName
+34 -1
Ver Arquivo
@@ -5,29 +5,55 @@ fsUtils = require 'fs-utils'
pathWatcher = require 'pathwatcher'
_ = require 'underscore'
# Public: Represents an individual file in the editor.
#
# The entry point for this class is in two locations:
# * {Buffer}, which associates text contents with a file
# * {Directory}, which associcates the children of a directory as files
module.exports =
class File
path: null
cachedContents: null
# Public: Creates a new file.
#
# path - A {String} representing the file path
# symlink - A {Boolean} indicating if the path is a symlink (default: false)
constructor: (@path, @symlink=false) ->
try
if fs.statSync(@path).isDirectory()
throw new Error("#{@path} is a directory")
# Public: Sets the path for the file.
#
# path - A {String} representing the new file path
setPath: (@path) ->
# Public: Retrieves the path for the file.
#
# Returns a {String}.
getPath: -> @path
# Public: Gets the file's basename--that is, the file without any directory information.
#
# Returns a {String}.
getBaseName: ->
fsUtils.base(@path)
# Public: Writes (and saves) new contents to the file.
#
# text - A {String} representing the new contents.
write: (text) ->
previouslyExisted = @exists()
@cachedContents = text
fsUtils.write(@getPath(), text)
@subscribeToNativeChangeEvents() if not previouslyExisted and @subscriptionCount() > 0
# Public: Reads the file.
#
# flushCache - A {Boolean} indicating if the cache should be erased--_i.e._, a force read is performed
#
# Returns a {String}.
read: (flushCache)->
if not @exists()
@cachedContents = null
@@ -36,12 +62,19 @@ class File
else
@cachedContents
# Public: Checks to see if a file exists.
#
# Returns a {Boolean}.
exists: ->
fsUtils.exists(@getPath())
###
# Internal #
###
afterSubscribe: ->
@subscribeToNativeChangeEvents() if @exists() and @subscriptionCount() == 1
afterUnsubscribe: ->
@unsubscribeFromNativeChangeEvents() if @subscriptionCount() == 0
+26
Ver Arquivo
@@ -1,6 +1,10 @@
Range = require 'range'
Point = require 'point'
# Public: Represents a fold that's hiding text from the screen.
#
# Folds are the primary reason that screen ranges and buffer ranges vary. Their
# creation is managed by the {DisplayBuffer}.
module.exports =
class Fold
@idCounter: 1
@@ -9,6 +13,10 @@ class Fold
startRow: null
endRow: null
###
# Internal #
###
constructor: (@displayBuffer, @startRow, @endRow) ->
@id = @constructor.idCounter++
@@ -18,6 +26,11 @@ class Fold
inspect: ->
"Fold(#{@startRow}, #{@endRow})"
# Public: Retrieves the buffer row range that a fold occupies.
#
# includeNewline - A {Boolean} which, if `true`, includes the trailing newline
#
# Returns a {Range}.
getBufferRange: ({includeNewline}={}) ->
if includeNewline
end = [@endRow + 1, 0]
@@ -26,6 +39,9 @@ class Fold
new Range([@startRow, 0], end)
# Public: Retrieves the number of buffer rows a fold occupies.
#
# Returns a {Number}.
getBufferRowCount: ->
@endRow - @startRow + 1
@@ -43,9 +59,19 @@ class Fold
@displayBuffer.unregisterFold(oldStartRow, this)
@displayBuffer.registerFold(this)
# Public: Identifies if a {Range} occurs within a fold.
#
# range - A {Range} to check
#
# Returns a {Boolean}.
isContainedByRange: (range) ->
range.start.row <= @startRow and @endRow <= range.end.row
# Public: Identifies if a fold is nested within a fold.
#
# fold - A {Fold} to check
#
# Returns a {Boolean}.
isContainedByFold: (fold) ->
@isContainedByRange(fold.getBufferRange())
+148 -24
Ver Arquivo
@@ -5,8 +5,18 @@ EventEmitter = require 'event-emitter'
RepositoryStatusTask = require 'repository-status-task'
GitUtils = require 'git-utils'
# Public: Represents the underlying git operations performed by Atom.
#
# Ultimately, this is an overlay to the native [git-utils](https://github.com/atom/node-git) model.
module.exports =
class Git
# Public: Creates a new `Git` instance.
#
# path - The git repository to open
# options - A hash with one key:
# :refreshOnWindowFocus - A {Boolean} that identifies if the windows should refresh
#
# Returns a new {Git} object.
@open: (path, options) ->
return null unless path
try
@@ -18,6 +28,15 @@ class Git
upstream: null
statusTask: null
###
# Internal #
###
# Internal: Creates a new `Git` object.
#
# path - The {String} representing the path to your git working directory
# options - A hash with the following keys:
# :refreshOnWindowFocus - If `true`, {#refreshIndex} and {#refreshStatus} are called on focus
constructor: (path, options={}) ->
@repo = GitUtils.open(path)
unless @repo?
@@ -40,16 +59,6 @@ class Git
@subscribe buffer, 'saved', bufferStatusHandler
@subscribe buffer, 'reloaded', bufferStatusHandler
getRepo: ->
unless @repo?
throw new Error("Repository has been destroyed")
@repo
refreshIndex: -> @getRepo().refreshIndex()
getPath: ->
@path ?= fsUtils.absolute(@getRepo().getPath())
destroy: ->
if @statusTask?
@statusTask.abort()
@@ -62,12 +71,46 @@ class Git
@unsubscribe()
###
# Public #
###
# Public: Retrieves the git repository.
#
# Returns a new `Repository`.
getRepo: ->
unless @repo?
throw new Error("Repository has been destroyed")
@repo
# Public: Reread the index to update any values that have changed since the last time the index was read.
refreshIndex: -> @getRepo().refreshIndex()
# Public: Retrieves the path of the repository.
#
# Returns a {String}.
getPath: ->
@path ?= fsUtils.absolute(@getRepo().getPath())
# Public: Retrieves the working directory of the repository.
#
# Returns a {String}.
getWorkingDirectory: ->
@getRepo().getWorkingDirectory()
# Public: Retrieves the reference or SHA-1 that `HEAD` points to.
#
# This can be `refs/heads/master`, or a full SHA-1 if the repository is in a detached `HEAD` state.
#
# Returns a {String}.
getHead: ->
@getRepo().getHead() ? ''
# Public: Retrieves the status of a single path in the repository.
#
# path - An {String} defining a relative path
#
# Returns a {Number}.
getPathStatus: (path) ->
currentPathStatus = @statuses[path] ? 0
pathStatus = @getRepo().getStatus(@relativize(path)) ? 0
@@ -79,38 +122,132 @@ class Git
@trigger 'status-changed', path, pathStatus
pathStatus
# Public: Identifies if a path is ignored.
#
# path - The {String} path to check
#
# Returns a {Boolean}.
isPathIgnored: (path) ->
@getRepo().isIgnored(@relativize(path))
# Public: Identifies if a value represents a status code.
#
# status - The code {Number} to check
#
# Returns a {Boolean}.
isStatusModified: (status) ->
@getRepo().isStatusModified(status)
# Public: Identifies if a path was modified.
#
# path - The {String} path to check
#
# Returns a {Boolean}.
isPathModified: (path) ->
@isStatusModified(@getPathStatus(path))
# Public: Identifies if a status code represents a new path.
#
# status - The code {Number} to check
#
# Returns a {Boolean}.
isStatusNew: (status) ->
@getRepo().isStatusNew(status)
# Public: Identifies if a path is new.
#
# path - The {String} path to check
#
# Returns a {Boolean}.
isPathNew: (path) ->
@isStatusNew(@getPathStatus(path))
# Public: Makes a path relative to the repository's working directory.
#
# path - The {String} path to convert
#
# Returns a {String}.
relativize: (path) ->
@getRepo().relativize(path)
# Public: Retrieves a shortened version of {.getHead}.
#
# This removes the leading segments of `refs/heads`, `refs/tags`, or `refs/remotes`.
# It also shortenes the SHA-1 of a detached `HEAD` to 7 characters.
#
# Returns a {String}.
getShortHead: ->
@getRepo().getShortHead()
# Public: Restore the contents of a path in the working directory and index to the version at `HEAD`.
#
# This is essentially the same as running:
# ```
# git reset HEAD -- <path>
# git checkout HEAD -- <path>
# ```
#
# path - The {String} path to checkout
#
# Returns a {Boolean} that's `true` if the method was successful.
checkoutHead: (path) ->
headCheckedOut = @getRepo().checkoutHead(@relativize(path))
@getPathStatus(path) if headCheckedOut
headCheckedOut
# Public: Retrieves the number of lines added and removed to a path.
#
# This compares the working directory contents of the path to the `HEAD` version.
#
# path - The {String} path to check
#
# Returns an object with two keys, `added` and `deleted`. These will always be greater than 0.
getDiffStats: (path) ->
@getRepo().getDiffStats(@relativize(path))
# Public: Identifies if a path is a submodule.
#
# path - The {String} path to check
#
# Returns a {Boolean}.
isSubmodule: (path) ->
@getRepo().isSubmodule(@relativize(path))
# Public: Retrieves the status of a directory.
#
# path - The {String} path to check
#
# Returns a {Number} representing the status.
getDirectoryStatus: (directoryPath) ->
directoryPath = "#{directoryPath}/"
directoryStatus = 0
for path, status of @statuses
directoryStatus |= status if path.indexOf(directoryPath) is 0
directoryStatus
# Public: Retrieves the number of commits the `HEAD` branch is ahead/behind the remote branch it is tracking.
#
# This is similar to the commit numbers reported by `git status` when a remote tracking branch exists.
#
# Returns an object with two keys, `ahead` and `behind`. These will always be greater than zero.
getAheadBehindCounts: ->
@getRepo().getAheadBehindCount()
# Public: Retrieves the line diffs comparing the `HEAD` version of the given path and the given text.
#
# This is similar to the commit numbers reported by `git status` when a remote tracking branch exists.
#
# path - The {String} path (relative to the repository)
# text - The {String} to compare against the `HEAD` contents
#
# Returns an object with two keys, `ahead` and `behind`. These will always be greater than zero.
getLineDiffs: (path, text) ->
@getRepo().getLineDiffs(@relativize(path), text)
###
# Internal #
###
refreshStatus: ->
if @statusTask?
@statusTask.off()
@@ -122,19 +259,6 @@ class Git
@statusTask.one 'task-completed', =>
@statusTask = null
@statusTask.start()
getDirectoryStatus: (directoryPath) ->
directoryPath = "#{directoryPath}/"
directoryStatus = 0
for path, status of @statuses
directoryStatus |= status if path.indexOf(directoryPath) is 0
directoryStatus
getAheadBehindCounts: ->
@getRepo().getAheadBehindCount()
getLineDiffs: (path, text) ->
@getRepo().getLineDiffs(@relativize(path), text)
_.extend Git.prototype, Subscriber
_.extend Git.prototype, EventEmitter
+27 -6
Ver Arquivo
@@ -3,15 +3,22 @@ Range = require 'range'
$ = require 'jquery'
_ = require 'underscore'
# Public: Represents the portion of the {Editor} containing row numbers.
#
# The gutter also indicates if rows are folded.
module.exports =
class Gutter extends View
###
# Internal #
###
@content: ->
@div class: 'gutter', =>
@div outlet: 'lineNumbers', class: 'line-numbers'
firstScreenRow: Infinity
lastScreenRow: -1
highestNumberWidth: null
afterAttach: (onDom) ->
return if @attached or not onDom
@@ -22,9 +29,6 @@ class Gutter extends View
@getEditor().on 'selection:changed', highlightLines
@on 'mousedown', (e) => @handleMouseEvents(e)
getEditor: ->
@parentView
beforeRemove: ->
$(document).off(".gutter-#{@getEditor().id}")
@@ -46,9 +50,26 @@ class Gutter extends View
$(document).on "mousemove.gutter-#{@getEditor().id}", moveHandler
$(document).one "mouseup.gutter-#{@getEditor().id}", => $(document).off 'mousemove', moveHandler
###
# Public #
###
# Public: Retrieves the containing {Editor}.
#
# Returns an {Editor}.
getEditor: ->
@parentView
# Public: Defines whether to show the gutter or not.
#
# showLineNumbers - A {Boolean} which, if `false`, hides the gutter
setShowLineNumbers: (showLineNumbers) ->
if showLineNumbers then @lineNumbers.show() else @lineNumbers.hide()
###
# Internal #
###
updateLineNumbers: (changes, renderFrom, renderTo) ->
if renderFrom < @firstScreenRow or renderTo > @lastScreenRow
performUpdate = true
@@ -61,7 +82,7 @@ class Gutter extends View
break
@renderLineNumbers(renderFrom, renderTo) if performUpdate
renderLineNumbers: (startScreenRow, endScreenRow) ->
editor = @getEditor()
maxDigits = editor.getLineCount().toString().length
@@ -76,7 +97,7 @@ class Gutter extends View
rowValue = (row + 1).toString()
classes = ['line-number']
classes.push('fold') if editor.isFoldedAtBufferRow(row)
@div lineNumber: row, class: classes.join(' '), =>
@div linenumber: row, class: classes.join(' '), =>
rowValuePadding = _.multiplyString('&nbsp;', maxDigits - rowValue.length)
@raw("#{rowValuePadding}#{rowValue}")
+28
Ver Arquivo
@@ -1,10 +1,18 @@
fsUtils = require 'fs-utils'
_ = require 'underscore'
# Public: Manages the states between {Editor}s, images, and the project as a whole.
#
# Essentially, the graphical version of a {EditSession}.
module.exports=
class ImageEditSession
registerDeserializer(this)
# Public: Identifies if a path can be opened by the image viewer.
#
# path - The {String} name of the path to check
#
# Returns a {Boolean}.
@canOpen: (path) ->
_.indexOf([
'.gif'
@@ -13,6 +21,10 @@ class ImageEditSession
'.png'
], fsUtils.extension(path), true) >= 0
###
# Internal #
###
@deserialize: (state) ->
if fsUtils.exists(state.path)
project.buildEditSession(state.path)
@@ -28,15 +40,31 @@ class ImageEditSession
getViewClass: ->
require 'image-view'
# Public: Retrieves the filename of the open file.
#
# This is `'untitled'` if the file is new and not saved to the disk.
#
# Returns a {String}.
getTitle: ->
if path = @getPath()
fsUtils.base(path)
else
'untitled'
# Public: Retrieves the URI of the current image.
#
# Returns a {String}.
getUri: -> @path
# Public: Retrieves the path of the current image.
#
# Returns a {String}.
getPath: -> @path
# Public: Compares two `ImageEditSession`s to determine equality.
#
# Equality is based on the condition that the two URIs are the same.
#
# Returns a {Boolean}.
isEqual: (other) ->
other instanceof ImageEditSession and @getUri() is other.getUri()
+38 -15
Ver Arquivo
@@ -2,12 +2,16 @@ ScrollView = require 'scroll-view'
_ = require 'underscore'
$ = require 'jquery'
# Public: Renders images in the {Editor}.
module.exports =
class ImageView extends ScrollView
# Internal:
@content: ->
@div class: 'image-view', tabindex: -1, =>
@img outlet: 'image'
# Internal:
initialize: (imageEditSession) ->
super
@@ -25,6 +29,7 @@ class ImageView extends ScrollView
@command 'image-view:zoom-out', => @zoomOut()
@command 'image-view:reset-zoom', => @resetZoom()
# Internal:
afterAttach: (onDom) ->
return unless onDom
@@ -35,6 +40,7 @@ class ImageView extends ScrollView
@active = @is(pane.activeView)
@centerImage() if @active and not wasActive
# Public: Places the image in the center of the {Editor}.
centerImage: ->
return unless @loaded and @isVisible()
@@ -43,6 +49,9 @@ class ImageView extends ScrollView
'left': Math.max((@width() - @image.outerWidth()) / 2, 0)
@image.show()
# Public: Indicates the path of the image.
#
# path - A {String} for the new image path.
setPath: (path) ->
if path?
if @image.attr('src') isnt path
@@ -51,12 +60,36 @@ class ImageView extends ScrollView
else
@image.hide()
setModel: (imageEditSession) ->
@setPath(imageEditSession?.getPath())
# Public: Retrieve's the {Editor}'s pane.
#
# Returns a {Pane}.
getPane: ->
@parent('.item-views').parent('.pane').view()
# Public: Zooms the image out.
#
# This is done by a factor of `0.9`.
zoomOut: ->
@adjustSize(0.9)
# Public: Zooms the image in.
#
# This is done by a factor of `1.1`.
zoomIn: ->
@adjustSize(1.1)
# Public: Zooms the image to its normal width and height.
resetZoom: ->
return unless @loaded and @isVisible()
@image.width(@originalWidth)
@image.height(@originalHeight)
@centerImage()
###
# Internal #
###
adjustSize: (factor) ->
return unless @loaded and @isVisible()
@@ -66,15 +99,5 @@ class ImageView extends ScrollView
@image.height(newHeight)
@centerImage()
zoomOut: ->
@adjustSize(0.9)
zoomIn: ->
@adjustSize(1.1)
resetZoom: ->
return unless @loaded and @isVisible()
@image.width(@originalWidth)
@image.height(@originalHeight)
@centerImage()
setModel: (imageEditSession) ->
@setPath(imageEditSession?.getPath())
+19 -6
Ver Arquivo
@@ -5,6 +5,19 @@ CSON = require 'cson'
BindingSet = require 'binding-set'
# Internal: Associates keymaps with actions.
#
# Keymaps are defined in a CSON format. A typical keymap looks something like this:
#
# ```cson
# 'body':
# 'ctrl-l': 'package:do-something'
#'.someClass':
# 'enter': 'package:confirm'
# ```
#
# As a key, you define the DOM element you want to work on, using CSS notation. For that
# key, you define one or more key:value pairs, associating keystrokes with a command to execute.
module.exports =
class Keymap
bindingSets: null
@@ -39,23 +52,23 @@ class Keymap
loadDirectory: (directoryPath) ->
@load(filePath) for filePath in fsUtils.list(directoryPath, ['.cson', '.json'])
load: (path) ->
@add(path, CSON.readObject(path))
add: (args...) ->
name = args.shift() if args.length > 1
keymap = args.shift()
for selector, bindings of keymap
@bindKeys(name, selector, bindings)
remove: (name) ->
for bindingSet in @bindingSets.filter((bindingSet) -> bindingSet.name is name)
_.remove(@bindingSets, bindingSet)
for keystrokes of bindingSet.commandsByKeystrokes
keystroke = keystrokes.split(' ')[0]
_.remove(@bindingSetsByFirstKeystroke[keystroke], bindingSet)
bindKeys: (args...) ->
name = args.shift() if args.length > 2
[selector, bindings] = args
@@ -65,7 +78,7 @@ class Keymap
keystroke = keystrokes.split(' ')[0] # only index by first keystroke
@bindingSetsByFirstKeystroke[keystroke] ?= []
@bindingSetsByFirstKeystroke[keystroke].push(bindingSet)
unbindKeys: (selector, bindings) ->
bindingSet = _.detect @bindingSets, (bindingSet) ->
bindingSet.selector is selector and bindingSet.bindings is bindings
@@ -73,7 +86,7 @@ class Keymap
if bindingSet
console.log "binding set", bindingSet
_.remove(@bindingSets, bindingSet)
bindingsForElement: (element) ->
keystrokeMap = {}
currentNode = $(element)
+39 -1
Ver Arquivo
@@ -5,6 +5,10 @@ require 'underscore-extensions'
EventEmitter = require 'event-emitter'
Subscriber = require 'subscriber'
###
# Internal #
###
module.exports =
class LanguageMode
buffer = null
@@ -12,20 +16,26 @@ class LanguageMode
editSession = null
currentGrammarScore: null
# Public: Sets up a `LanguageMode` for the given {EditSession}.
#
# editSession - The {EditSession} to associate with
constructor: (@editSession) ->
@buffer = @editSession.buffer
@reloadGrammar()
@subscribe syntax, 'grammar-added', (grammar) =>
newScore = grammar.getScore(@buffer.getPath(), @buffer.getText())
@setGrammar(grammar, newScore) if newScore > @currentGrammarScore
# Internal:
destroy: ->
@unsubscribe()
setGrammar: (grammar, score) ->
return if grammar is @grammar
@unsubscribe(@grammar) if @grammar
@grammar = grammar
@currentGrammarScore = score ? grammar.getScore(@buffer.getPath(), @buffer.getText())
@subscribe @grammar, 'grammar-updated', => @trigger 'grammar-updated'
@trigger 'grammar-changed', grammar
reloadGrammar: ->
@@ -34,6 +44,14 @@ class LanguageMode
else
throw new Error("No grammar found for path: #{path}")
# Public: Wraps the lines between two rows in comments.
#
# If the language doesn't have comment, nothing happens.
#
# startRow - The row {Number} to start at
# endRow - The row {Number} to end at
#
# Returns an {Array} of the commented {Ranges}.
toggleLineCommentsForBufferRows: (start, end) ->
scopes = @editSession.scopesForBufferPosition([start, 0])
return unless commentStartString = syntax.getProperty(scopes, "editor.commentStart")
@@ -96,6 +114,13 @@ class LanguageMode
[bufferRow, foldEndRow]
# Public: Given a buffer row, this returns a suggested indentation level.
#
# The indentation level provided is based on the current {LanguageMode}.
#
# bufferRow - A {Number} indicating the buffer row
#
# Returns a {Number}.
suggestedIndentForBufferRow: (bufferRow) ->
currentIndentLevel = @editSession.indentationForBufferRow(bufferRow)
scopes = @editSession.scopesForBufferPosition([bufferRow, 0])
@@ -115,13 +140,23 @@ class LanguageMode
Math.max(desiredIndentLevel, currentIndentLevel)
# Public: Indents all the rows between two buffer row numbers.
#
# startRow - The row {Number} to start at
# endRow - The row {Number} to end at
autoIndentBufferRows: (startRow, endRow) ->
@autoIndentBufferRow(row) for row in [startRow..endRow]
# Public: Given a buffer row, this indents it.
#
# bufferRow - The row {Number}
autoIndentBufferRow: (bufferRow) ->
@autoIncreaseIndentForBufferRow(bufferRow)
@autoDecreaseIndentForBufferRow(bufferRow)
# Public: Given a buffer row, this increases the indentation.
#
# bufferRow - The row {Number}
autoIncreaseIndentForBufferRow: (bufferRow) ->
precedingRow = @buffer.previousNonBlankRow(bufferRow)
return unless precedingRow?
@@ -137,6 +172,9 @@ class LanguageMode
if desiredIndentLevel > currentIndentLevel
@editSession.setIndentationForBufferRow(bufferRow, desiredIndentLevel)
# Public: Given a buffer row, this decreases the indentation.
#
# bufferRow - The row {Number}
autoDecreaseIndentForBufferRow: (bufferRow) ->
scopes = @editSession.scopesForBufferPosition([bufferRow, 0])
increaseIndentRegex = @increaseIndentRegexForScopes(scopes)
+50 -4
Ver Arquivo
@@ -1,6 +1,8 @@
Point = require 'point'
Range = require 'range'
_ = require 'underscore'
# Internal: Responsible for doing the translations between screen positions and buffer positions.
module.exports =
class LineMap
maxScreenLineLength: 0
@@ -21,17 +23,35 @@ class LineMap
@maxScreenLineLength = 0
maxLengthCandidates = @screenLines
@screenLines.splice(startRow, rowCount, screenLines...)
_.spliceWithArray(@screenLines, startRow, rowCount, screenLines)
for screenLine in maxLengthCandidates
@maxScreenLineLength = Math.max(@maxScreenLineLength, screenLine.text.length)
# Public: Gets the line for the given screen row.
#
# screenRow - A {Number} indicating the screen row.
#
# Returns a {ScreenLine}.
lineForScreenRow: (row) ->
@screenLines[row]
# Public: Gets the lines for the given screen row boundaries.
#
# start - A {Number} indicating the beginning screen row.
# end - A {Number} indicating the ending screen row.
#
# Returns an {Array} of {ScreenLine}s.
linesForScreenRows: (startRow, endRow) ->
@screenLines[startRow..endRow]
# Public: Given starting and ending screen rows, this returns an array of the
# buffer rows corresponding to every screen row in the range
#
# startRow - The screen row {Number} to start at
# endRow - The screen row {Integer} to end at (default: {.lastScreenRow})
#
# Returns an {Array} of buffer rows as {Integers}s.
bufferRowsForScreenRows: (startRow, endRow=@lastScreenRow()) ->
bufferRows = []
bufferRow = 0
@@ -45,6 +65,9 @@ class LineMap
screenLineCount: ->
@screenLines.length
# Retrieves the last screen row in the buffer.
#
# Returns an {Integer}.
lastScreenRow: ->
@screenLineCount() - 1
@@ -77,6 +100,13 @@ class LineMap
column = screenLine.clipScreenColumn(column, options)
new Point(row, column)
# Public: Given a buffer position, this converts it into a screen position.
#
# bufferPosition - An object that represents a buffer position. It can be either
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
# options - The same options available to {.clipScreenPosition}.
#
# Returns a {Point}.
screenPositionForBufferPosition: (bufferPosition, options={}) ->
{ row, column } = Point.fromObject(bufferPosition)
[screenRow, screenLines] = @screenRowAndScreenLinesForBufferRow(row)
@@ -109,9 +139,15 @@ class LineMap
currentBufferRow = nextBufferRow
[screenRow, screenLines]
# Public: Given a buffer range, this converts it into a screen position.
#
# screenPosition - An object that represents a buffer position. It can be either
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
# options - The same options available to {.clipScreenPosition}.
#
# Returns a {Point}.
bufferPositionForScreenPosition: (screenPosition, options) ->
{ row, column } = @clipScreenPosition(Point.fromObject(screenPosition))
{ row, column } = @clipScreenPosition(Point.fromObject(screenPosition), options)
[bufferRow, screenLine] = @bufferRowAndScreenLineForScreenRow(row)
bufferColumn = screenLine.bufferColumnForScreenColumn(column)
new Point(bufferRow, bufferColumn)
@@ -126,18 +162,28 @@ class LineMap
[bufferRow, screenLine]
# Public: Given a buffer range, this converts it into a screen position.
#
# bufferRange - The {Range} to convert
#
# Returns a {Range}.
screenRangeForBufferRange: (bufferRange) ->
bufferRange = Range.fromObject(bufferRange)
start = @screenPositionForBufferPosition(bufferRange.start)
end = @screenPositionForBufferPosition(bufferRange.end)
new Range(start, end)
# Public: Given a screen range, this converts it into a buffer position.
#
# screenRange - The {Range} to convert
#
# Returns a {Range}.
bufferRangeForScreenRange: (screenRange) ->
screenRange = Range.fromObject(screenRange)
start = @bufferPositionForScreenPosition(screenRange.start)
end = @bufferPositionForScreenPosition(screenRange.end)
new Range(start, end)
# Internal:
logLines: (start=0, end=@screenLineCount() - 1)->
for row in [start..end]
line = @lineForScreenRow(row).text
+9
Ver Arquivo
@@ -1,5 +1,10 @@
Token = require 'token'
EventEmitter = require 'event-emitter'
_ = require 'underscore'
###
# Internal #
###
module.exports =
class NullGrammar
name: 'Null Grammar'
@@ -9,3 +14,7 @@ class NullGrammar
tokenizeLine: (line) ->
{ tokens: [new Token(value: line, scopes: ['null-grammar.text.plain'])] }
grammarUpdated: -> # noop
_.extend NullGrammar.prototype, EventEmitter
+3
Ver Arquivo
@@ -1,5 +1,8 @@
fsUtils = require 'fs-utils'
###
# Internal #
###
module.exports =
class Package
@build: (path) ->
+2
Ver Arquivo
@@ -1,8 +1,10 @@
$ = require 'jquery'
{View} = require 'space-pen'
# Internal:
module.exports =
class PaneAxis extends View
@deserialize: ({children}) ->
childViews = children.map (child) -> deserialize(child)
new this(childViews)
+2
Ver Arquivo
@@ -2,8 +2,10 @@ $ = require 'jquery'
_ = require 'underscore'
PaneAxis = require 'pane-axis'
# Internal:
module.exports =
class PaneColumn extends PaneAxis
@content: ->
@div class: 'column'
+8
Ver Arquivo
@@ -6,6 +6,10 @@ module.exports =
class PaneContainer extends View
registerDeserializer(this)
###
# Internal #
###
@deserialize: ({root}) ->
container = new PaneContainer
container.append(deserialize(root)) if root
@@ -21,6 +25,10 @@ class PaneContainer extends View
serialize: ->
deserializer: 'PaneContainer'
root: @getRoot()?.serialize()
###
# Public #
###
focusNextPane: ->
panes = @getPanes()
+4
Ver Arquivo
@@ -2,6 +2,10 @@ $ = require 'jquery'
_ = require 'underscore'
PaneAxis = require 'pane-axis'
###
# Internal #
###
module.exports =
class PaneRow extends PaneAxis
@content: ->
+9
Ver Arquivo
@@ -6,6 +6,11 @@ PaneColumn = require 'pane-column'
module.exports =
class Pane extends View
###
# Internal #
###
@content: (wrappedView) ->
@div class: 'pane', =>
@div class: 'item-views', outlet: 'itemViews'
@@ -60,6 +65,10 @@ class Pane extends View
@attached = true
@trigger 'pane:attached', [this]
###
# Public #
###
makeActive: ->
for pane in @getContainer().getPanes() when pane isnt this
pane.makeInactive()
+13
Ver Arquivo
@@ -1,17 +1,30 @@
crypto = require 'crypto'
# Internal: Represents the clipboard used for copying and pasting in Atom.
module.exports =
class Pasteboard
signatureForMetadata: null
# Internal: Creates an `md5` hash of some text.
#
# text - A {String} to encrypt.
#
# Returns an encrypted {String}.
md5: (text) ->
crypto.createHash('md5').update(text, 'utf8').digest('hex')
# Saves from the clipboard.
#
# text - A {String} to store
# metadata - An object of additional info to associate with the text
write: (text, metadata) ->
@signatureForMetadata = @md5(text)
@metadata = metadata
$native.writeToPasteboard(text)
# Loads from the clipboard.
#
# Returns an {Array}. The first index is the saved text, and the second is any metadata associated with the text.
read: ->
text = $native.readFromPasteboard()
value = [text]
+96 -3
Ver Arquivo
@@ -1,7 +1,16 @@
_ = require 'underscore'
# Public: Represents a coordinate in the editor.
#
# Each `Point` is actually an object with two properties: `row` and `column`.
module.exports =
class Point
# Public: Constructs a `Point` from a given object.
#
# object - This can be an {Array} (`[startRow, startColumn, endRow, endColumn]`) or an object `{row, column}`
#
# Returns the new {Point}.
@fromObject: (object) ->
if object instanceof Point
object
@@ -13,6 +22,15 @@ class Point
new Point(row, column)
# Public: Identifies which `Point` is smaller.
#
# "Smaller" means that both the `row` and `column` values of one `Point` are less than or equal
# to the other.
#
# point1 - The first {Point} to check
# point2 - The second {Point} to check
#
# Returns the smaller {Point}.
@min: (point1, point2) ->
point1 = @fromObject(point1)
point2 = @fromObject(point2)
@@ -21,11 +39,25 @@ class Point
else
point2
# Public: Creates a new `Point` object.
#
# row - A {Number} indicating the row (default: 0)
# column - A {Number} indicating the column (default: 0)
#
# Returns a {Point},
constructor: (@row=0, @column=0) ->
# Public: Creates an identical copy of the `Point`.
#
# Returns a duplicate {Point}.
copy: ->
new Point(@row, @column)
# Public: Adds the `column`s of two `Point`s together.
#
# other - The {Point} to add with
#
# Returns the new {Point}.
add: (other) ->
other = Point.fromObject(other)
row = @row + other.row
@@ -36,10 +68,24 @@ class Point
new Point(row, column)
# Public: Moves a `Point`.
#
# In other words, the `row` values and `column` values are added to each other.
#
# other - The {Point} to add with
#
# Returns the new {Point}.
translate: (other) ->
other = Point.fromObject(other)
new Point(@row + other.row, @column + other.column)
# Public: Creates two new `Point`s, split down a `column` value.
#
# In other words, given a point, this creates `Point(0, column)` and `Point(row, column)`.
#
# column - The {Number} to split at
#
# Returns an {Array} of two {Point}s.
splitAt: (column) ->
if @row == 0
rightColumn = @column - column
@@ -48,6 +94,17 @@ class Point
[new Point(0, column), new Point(@row, rightColumn)]
# Internal: Compares two `Point`s.
#
# other - The {Point} to compare against
#
# Returns a {Number} matching the following rules:
# * If the first `row` is greater than `other.row`, returns `1`.
# * If the first `row` is less than `other.row`, returns `-1`.
# * If the first `column` is greater than `other.column`, returns `1`.
# * If the first `column` is less than `other.column`, returns `-1`.
#
# Otherwise, returns `0`.
compare: (other) ->
if @row > other.row
1
@@ -61,31 +118,67 @@ class Point
else
0
# Public: Identifies if two `Point`s are equal.
#
# other - The {Point} to compare against
#
# Returns a {Boolean}.
isEqual: (other) ->
return false unless other
other = Point.fromObject(other)
@row == other.row and @column == other.column
# Public: Identifies if one `Point` is less than another.
#
# other - The {Point} to compare against
#
# Returns a {Boolean}.
isLessThan: (other) ->
@compare(other) < 0
# Public: Identifies if one `Point` is less than or equal to another.
#
# other - The {Point} to compare against
#
# Returns a {Boolean}.
isLessThanOrEqual: (other) ->
@compare(other) <= 0
# Public: Identifies if one `Point` is greater than another.
#
# other - The {Point} to compare against
#
# Returns a {Boolean}.
isGreaterThan: (other) ->
@compare(other) > 0
# Public: Identifies if one `Point` is greater than or equal to another.
#
# other - The {Point} to compare against
#
# Returns a {Boolean}.
isGreaterThanOrEqual: (other) ->
@compare(other) >= 0
inspect: ->
"(#{@row}, #{@column})"
# Public: Converts the {Point} to a String.
#
# Returns a {String}.
toString: ->
"#{@row},#{@column}"
# Public: Converts the {Point} to an Array.
#
# Returns an {Array}.
toArray: ->
[@row, @column]
###
# Internal #
###
inspect: ->
"(#{@row}, #{@column})"
# Internal:
serialize: ->
@toArray()
+123 -17
Ver Arquivo
@@ -9,13 +9,14 @@ EventEmitter = require 'event-emitter'
Directory = require 'directory'
BufferedProcess = require 'buffered-process'
# Public: Represents a project that's opened in Atom.
#
# Ultimately, a project is a git directory that's been opened. It's a collection
# of directories and files that you can operate on.
module.exports =
class Project
registerDeserializer(this)
@deserialize: (state) ->
new Project(state.path)
tabLength: 2
softTabs: true
softWrap: false
@@ -23,21 +24,41 @@ class Project
editSessions: null
ignoredPathRegexes: null
# Public: Establishes a new project at a given path.
#
# path - The {String} name of the path
constructor: (path) ->
@setPath(path)
@editSessions = []
@buffers = []
###
# Internal #
###
serialize: ->
deserializer: 'Project'
path: @getPath()
@deserialize: (state) ->
new Project(state.path)
destroy: ->
editSession.destroy() for editSession in @getEditSessions()
###
# Public #
###
# Public: Retrieves the project path.
#
# Returns a {String}.
getPath: ->
@rootDirectory?.path
# Public: Sets the project path.
#
# path - A {String} representing the new path
setPath: (path) ->
@rootDirectory?.off()
@@ -49,9 +70,15 @@ class Project
@trigger "path-changed"
# Public: Retrieves the name of the root directory.
#
# Returns a {String}.
getRootDirectory: ->
@rootDirectory
# Public: Retrieves the names of every file (that's not `git ignore`d) in the project.
#
# Returns an {Array} of {String}s.
getFilePaths: ->
deferred = $.Deferred()
paths = []
@@ -61,6 +88,11 @@ class Project
deferred.resolve(paths)
deferred.promise()
# Public: Identifies if a path is ignored.
#
# path - The {String} name of the path to check
#
# Returns a {Boolean}.
isPathIgnored: (path) ->
for segment in path.split("/")
ignoredNames = config.get("core.ignoredNames") or []
@@ -68,29 +100,74 @@ class Project
@ignoreRepositoryPath(path)
# Public: Identifies if a path is ignored.
#
# path - The {String} name of the path to check
#
# Returns a {Boolean}.
ignoreRepositoryPath: (path) ->
config.get("core.hideGitIgnoredFiles") and git?.isPathIgnored(fsUtils.join(@getPath(), path))
# Public: Given a path, this resolves it relative to the project directory.
#
# filePath - The {String} name of the path to convert
#
# Returns a {String}.
resolve: (filePath) ->
filePath = fsUtils.join(@getPath(), filePath) unless filePath[0] == '/'
fsUtils.absolute filePath
# Public: Given a path, this makes it relative to the project directory.
#
# filePath - The {String} name of the path to convert
#
# Returns a {String}.
relativize: (fullPath) ->
return fullPath unless fullPath.lastIndexOf(@getPath()) is 0
fullPath.replace(@getPath(), "").replace(/^\//, '')
# Public: Identifies if the project is using soft tabs.
#
# Returns a {Boolean}.
getSoftTabs: -> @softTabs
# Public: Sets the project to use soft tabs.
#
# softTabs - A {Boolean} which, if `true`, sets soft tabs
setSoftTabs: (@softTabs) ->
# Public: Identifies if the project is using soft wrapping.
#
# Returns a {Boolean}.
getSoftWrap: -> @softWrap
# Public: Sets the project to use soft wrapping.
#
# softTabs - A {Boolean} which, if `true`, sets soft wrapping
setSoftWrap: (@softWrap) ->
# Public: Given a path to a file, this constructs and associates a new `EditSession`, showing the file.
#
# filePath - The {String} path of the file to associate with
# editSessionOptions - Options that you can pass to the `EditSession` constructor
#
# Returns either an {EditSession} (for text) or {ImageEditSession} (for images).
buildEditSession: (filePath, editSessionOptions={}) ->
if ImageEditSession.canOpen(filePath)
new ImageEditSession(filePath)
else
@buildEditSessionForBuffer(@bufferForPath(filePath), editSessionOptions)
# Public: Retrieves all the {EditSession}s in the project; that is, the `EditSession`s for all open files.
#
# Returns an {Array} of {EditSession}s.
getEditSessions: ->
new Array(@editSessions...)
###
# Internal #
###
buildEditSessionForBuffer: (buffer, editSessionOptions) ->
options = _.extend(@defaultEditSessionOptions(), editSessionOptions)
options.project = this
@@ -105,22 +182,10 @@ class Project
softTabs: @getSoftTabs()
softWrap: @getSoftWrap()
getEditSessions: ->
new Array(@editSessions...)
eachEditSession: (callback) ->
callback(editSession) for editSession in @getEditSessions()
@on 'edit-session-created', (editSession) -> callback(editSession)
removeEditSession: (editSession) ->
_.remove(@editSessions, editSession)
getBuffers: ->
buffers = []
for editSession in @editSessions when not _.include(buffers, editSession.buffer)
buffers.push editSession.buffer
buffers
eachBuffer: (args...) ->
subscriber = args.shift() if args.length > 1
callback = args.shift()
@@ -131,6 +196,34 @@ class Project
else
@on 'buffer-created', (buffer) -> callback(buffer)
###
# Public #
###
# Public: Removes an {EditSession} association from the project.
#
# Returns the removed {EditSession}.
removeEditSession: (editSession) ->
_.remove(@editSessions, editSession)
# Public: Retrieves all the {Buffer}s in the project; that is, the buffers for all open files.
#
# Returns an {Array} of {Buffer}s.
getBuffers: ->
buffers = []
for editSession in @editSessions when not _.include(buffers, editSession.buffer)
buffers.push editSession.buffer
buffers
# Public: Given a file path, this retrieves or creates a new {Buffer}.
#
# If the `filePath` already has a `buffer`, that value is used instead. Otherwise,
# `text` is used as the contents of the new buffer.
#
# filePath - A {String} representing a path. If `null`, an "Untitled" buffer is created.
# text - The {String} text to use as a buffer, if the file doesn't have any contents
#
# Returns the {Buffer}.
bufferForPath: (filePath, text) ->
if filePath?
filePath = @resolve(filePath)
@@ -140,15 +233,28 @@ class Project
else
@buildBuffer(null, text)
# Public: Given a file path, this sets its {Buffer}.
#
# filePath - A {String} representing a path
# text - The {String} text to use as a buffer
#
# Returns the {Buffer}.
buildBuffer: (filePath, text) ->
buffer = new Buffer(filePath, text)
@buffers.push buffer
@trigger 'buffer-created', buffer
buffer
# Public: Removes a {Buffer} association from the project.
#
# Returns the removed {Buffer}.
removeBuffer: (buffer) ->
_.remove(@buffers, buffer)
# Public: Performs a search across all the files in the project.
#
# regex - A {RegExp} to search with
# iterator - A {Function} callback on each file found
scan: (regex, iterator) ->
bufferedData = ""
state = 'readingPath'
@@ -196,7 +302,7 @@ class Project
readLine(line) if state is 'readingLines'
command = require.resolve('nak')
args = ['--ackmate', regex.source, @getPath()]
args = ['--hidden', '--ackmate', regex.source, @getPath()]
args.unshift("--addVCSIgnores") if config.get('core.excludeVcsIgnoredPaths')
new BufferedProcess({command, args, stdout, exit})
deferred
+100
Ver Arquivo
@@ -1,8 +1,20 @@
Point = require 'point'
_ = require 'underscore'
# Public: Indicates a region within the editor.
#
# To better visualize how this works, imagine a rectangle.
# Each quadrant of the rectangle is analogus to a range, as ranges contain a
# starting row and a starting column, as well as an ending row, and an ending column.
#
# Each `Range` is actually constructed of two `Point` objects, labelled `start` and `end`.
module.exports =
class Range
# Public: Constructs a `Range` from a given object.
#
# object - This can be an {Array} (`[startRow, startColumn, endRow, endColumn]`) or an object `{start: Point, end: Point}`
#
# Returns the new {Range}.
@fromObject: (object) ->
if _.isArray(object)
new Range(object...)
@@ -11,11 +23,22 @@ class Range
else
new Range(object.start, object.end)
# Public: Constructs a `Range` from a {Point}, and the delta values beyond that point.
#
# point - A {Point} to start with
# rowDelta - A {Number} indicating how far from the starting {Point} the range's row should be
# columnDelta - A {Number} indicating how far from the starting {Point} the range's column should be
#
# Returns the new {Range}.
@fromPointWithDelta: (point, rowDelta, columnDelta) ->
pointA = Point.fromObject(point)
pointB = new Point(point.row + rowDelta, point.column + columnDelta)
new Range(pointA, pointB)
# Public: Creates a new `Range` object based on two {Point}s.
#
# pointA - The first {Point} (default: `0, 0`)
# pointB - The second {Point} (default: `0, 0`)
constructor: (pointA = new Point(0, 0), pointB = new Point(0, 0)) ->
pointA = Point.fromObject(pointA)
pointB = Point.fromObject(pointB)
@@ -27,40 +50,95 @@ class Range
@start = pointB
@end = pointA
# Public: Creates an identical copy of the `Range`.
#
# Returns a duplicate {Range}.
copy: ->
new Range(@start.copy(), @end.copy())
# Public: Identifies if two `Range`s are equal.
#
# All four points (`start.row`, `start.column`, `end.row`, `end.column`) must be
# equal for this method to return `true`.
#
# other - A different {Range} to check against
#
# Returns a {Boolean}.
isEqual: (other) ->
if _.isArray(other) and other.length == 2
other = new Range(other...)
other.start.isEqual(@start) and other.end.isEqual(@end)
# Public: Identifies if the `Range` is on the same line.
#
# In other words, if `start.row` is equal to `end.row`.
#
# Returns a {Boolean}.
isSingleLine: ->
@start.row == @end.row
# Public: Identifies if two `Range`s are on the same line.
#
# other - A different {Range} to check against
#
# Returns a {Boolean}.
coversSameRows: (other) ->
@start.row == other.start.row && @end.row == other.end.row
# Internal:
inspect: ->
"[#{@start.inspect()} - #{@end.inspect()}]"
# Public: Adds a new point to the `Range`s `start` and `end`.
#
# point - A new {Point} to add
#
# Returns the new {Range}.
add: (point) ->
new Range(@start.add(point), @end.add(point))
# Public: Moves a `Range`.
#
# In other words, the starting and ending `row` values, and the starting and ending
# `column` values, are added to each other.
#
# startPoint - The {Point} to move the `Range`s `start` by
# endPoint - The {Point} to move the `Range`s `end` by
#
# Returns the new {Range}.
translate: (startPoint, endPoint=startPoint) ->
new Range(@start.translate(startPoint), @end.translate(endPoint))
# Public: Identifies if two `Range`s intersect each other.
#
# otherRange - A different {Range} to check against
#
# Returns a {Boolean}.
intersectsWith: (otherRange) ->
if @start.isLessThanOrEqual(otherRange.start)
@end.isGreaterThanOrEqual(otherRange.start)
else
otherRange.intersectsWith(this)
# Public: Identifies if a second `Range` is contained within a first.
#
# otherRange - A different {Range} to check against
# options - A hash with a single option:
# :exclusive - A {Boolean} which, if `true`, indicates that no {Point}s in the `Range` can be equal
#
# Returns a {Boolean}.
containsRange: (otherRange, {exclusive} = {}) ->
{ start, end } = Range.fromObject(otherRange)
@containsPoint(start, {exclusive}) and @containsPoint(end, {exclusive})
# Public: Identifies if a `Range` contains a {Point}.
#
# point - A {Point} to check against
# options - A hash with a single option:
# :exclusive - A {Boolean} which, if `true`, indicates that no {Point}s in the `Range` can be equal
#
# Returns a {Boolean}.
containsPoint: (point, {exclusive} = {}) ->
point = Point.fromObject(point)
if exclusive
@@ -68,17 +146,36 @@ class Range
else
point.isGreaterThanOrEqual(@start) and point.isLessThanOrEqual(@end)
# Public: Identifies if a `Range` contains a row.
#
# row - A row {Number} to check against
# options - A hash with a single option:
#
# Returns a {Boolean}.
containsRow: (row) ->
@start.row <= row <= @end.row
# Public: Constructs a union between two `Range`s.
#
# otherRange - A different {Range} to unionize with
#
# Returns the new {Range}.
union: (otherRange) ->
start = if @start.isLessThan(otherRange.start) then @start else otherRange.start
end = if @end.isGreaterThan(otherRange.end) then @end else otherRange.end
new Range(start, end)
# Public: Identifies if a `Range` is empty.
#
# A `Range` is empty if its start {Point} matches its end.
#
# Returns a {Boolean}.
isEmpty: ->
@start.isEqual(@end)
# Public: Calculates the difference between a `Range`s `start` and `end` points.
#
# Returns a {Point}.
toDelta: ->
rows = @end.row - @start.row
if rows == 0
@@ -87,5 +184,8 @@ class Range
columns = @end.column
new Point(rows, columns)
# Public: Calculates the number of rows a `Range`s contains.
#
# Returns a {Number}.
getRowCount: ->
@end.row - @start.row + 1
+2
Ver Arquivo
@@ -1,8 +1,10 @@
Task = require 'task'
_ = require 'underscore'
# Internal:
module.exports =
class RepositoryStatusTask extends Task
constructor: (@repo) ->
super('repository-status-handler')
+55 -4
Ver Arquivo
@@ -13,6 +13,7 @@ PaneRow = require 'pane-row'
PaneContainer = require 'pane-container'
EditSession = require 'edit-session'
# Public: The container for the entire Atom application.
module.exports =
class RootView extends View
registerDeserializers(this, Pane, PaneRow, PaneColumn, Editor)
@@ -23,12 +24,16 @@ class RootView extends View
ignoredNames: [".git", ".svn", ".DS_Store"]
disabledPackages: []
###
# Internal:
###
@content: ({panes}={}) ->
@div id: 'root-view', =>
@div id: 'horizontal', outlet: 'horizontal', =>
@div id: 'vertical', outlet: 'vertical', =>
@subview 'panes', panes ? new PaneContainer
@deserialize: ({ panes }) ->
panes = deserialize(panes) if panes?.deserializer is 'PaneContainer'
new RootView({panes})
@@ -74,9 +79,6 @@ class RootView extends View
deserializer: 'RootView'
panes: @panes.serialize()
confirmClose: ->
@panes.confirmClose()
handleFocus: (e) ->
if @getActivePane()
@getActivePane().focus()
@@ -93,6 +95,17 @@ class RootView extends View
afterAttach: (onDom) ->
@focus() if onDom
###
# Public #
###
# Public: Shows a dialog asking if the pane was _really_ meant to be closed.
confirmClose: ->
@panes.confirmClose()
# Public: Given a filepath, this opens it in Atom.
#
# Returns the `EditSession` for the file URI.
open: (path, options = {}) ->
changeFocus = options.changeFocus ? true
path = project.resolve(path) if path?
@@ -107,6 +120,7 @@ class RootView extends View
activePane.focus() if changeFocus
editSession
# Public: Updates the application's title, based on whichever file is open.
updateTitle: ->
if projectPath = project.getPath()
if item = @getActivePaneItem()
@@ -116,12 +130,21 @@ class RootView extends View
else
@setTitle('untitled')
# Public: Sets the application's title.
#
# Returns a {String}.
setTitle: (title) ->
document.title = title
# Public: Retrieves all of the application's {Editor}s.
#
# Returns an {Array} of {Editor}s.
getEditors: ->
@panes.find('.pane > .item-views > .editor').map(-> $(this).view()).toArray()
# Public: Retrieves all of the modified buffers that are open and unsaved.
#
# Returns an {Array} of {Buffer}s.
getModifiedBuffers: ->
modifiedBuffers = []
for pane in @getPanes()
@@ -129,9 +152,15 @@ class RootView extends View
modifiedBuffers.push item.buffer if item.buffer.isModified()
modifiedBuffers
# Public: Retrieves all of the paths to open files.
#
# Returns an {Array} of {String}s.
getOpenBufferPaths: ->
_.uniq(_.flatten(@getEditors().map (editor) -> editor.getOpenBufferPaths()))
# Public: Retrieves the pane that's currently open.
#
# Returns an {Pane}.
getActivePane: ->
@panes.getActivePane()
@@ -145,29 +174,51 @@ class RootView extends View
focusNextPane: -> @panes.focusNextPane()
getFocusedPane: -> @panes.getFocusedPane()
# Internal: Destroys everything.
remove: ->
editor.remove() for editor in @getEditors()
project.destroy()
super
# Public: Saves all of the open buffers.
saveAll: ->
@panes.saveAll()
# Public: Fires a callback on each open {Pane}.
#
# callback - A {Function} to call
eachPane: (callback) ->
@panes.eachPane(callback)
# Public: Retrieves all of the open {Pane}s.
#
# Returns an {Array} of {Pane}.
getPanes: ->
@panes.getPanes()
# Public: Given a {Pane}, this fetches its ID.
#
# pane - An open {Pane}
#
# Returns a {Number}.
indexOfPane: (pane) ->
@panes.indexOfPane(pane)
# Public: Fires a callback on each open {Editor}.
#
# callback - A {Function} to call
eachEditor: (callback) ->
callback(editor) for editor in @getEditors()
@on 'editor:attached', (e, editor) -> callback(editor)
# Public: Fires a callback on each open {EditSession}.
#
# callback - A {Function} to call
eachEditSession: (callback) ->
project.eachEditSession(callback)
# Public: Fires a callback on each open {Buffer}.
#
# callback - A {Function} to call
eachBuffer: (callback) ->
project.eachBuffer(callback)
+7
Ver Arquivo
@@ -1,5 +1,9 @@
_ = require 'underscore'
###
# Internal #
###
module.exports =
class ScreenLine
constructor: ({tokens, @lineEnding, @ruleStack, @bufferRows, @startBufferColumn, @fold, tabLength}) ->
@@ -104,6 +108,9 @@ class ScreenLine
breakOutLeadingWhitespace = token.isOnlyWhitespace() if breakOutLeadingWhitespace
outputTokens
# Public: Determins if the current line is commented.
#
# Returns a {Boolean}.
isComment: ->
for token in @tokens
continue if token.scopes.length is 1
+7
Ver Arquivo
@@ -1,6 +1,13 @@
{View} = require 'space-pen'
# Public: Represents a view that scrolls.
#
# This `View` subclass listens to events such as `page-up`, `page-down`,
# `move-to-top`, and `move-to-bottom`.
module.exports =
class ScrollView extends View
# Internal: The constructor.
initialize: ->
@on 'core:page-up', => @pageUp()
@on 'core:page-down', => @pageDown()
+5
Ver Arquivo
@@ -5,6 +5,11 @@ fuzzyFilter = require 'fuzzy-filter'
module.exports =
class SelectList extends View
###
# Internal #
###
@content: ->
@div class: @viewClass(), =>
@subview 'miniEditor', new Editor(mini: true)
+2
Ver Arquivo
@@ -4,6 +4,8 @@ Range = require 'range'
module.exports =
class SelectionView extends View
# Internal: Establishes the DOM for the selection view.
@content: ->
@div class: 'selection'
+120 -3
Ver Arquivo
@@ -2,6 +2,7 @@ Range = require 'range'
EventEmitter = require 'event-emitter'
_ = require 'underscore'
# Public: Represents a selection in the {EditSession}.
module.exports =
class Selection
cursor: null
@@ -12,6 +13,10 @@ class Selection
wordwise: false
needsAutoscroll: null
###
# Internal #
###
constructor: ({@cursor, @marker, @editSession, @goalBufferRange}) ->
@cursor.selection = this
@editSession.observeMarker @marker, => @screenRangeChanged()
@@ -32,27 +37,56 @@ class Selection
@wordwise = false
@linewise = false
clearAutoscroll: ->
@needsAutoscroll = null
###
# Public #
###
# Public: Identifies if the selection is highlighting anything.
#
# Returns a {Boolean}.
isEmpty: ->
@getBufferRange().isEmpty()
# Public: Identifies if the selection is reversed, that is, it is highlighting "up."
#
# Returns a {Boolean}.
isReversed: ->
@editSession.isMarkerReversed(@marker)
# Public: Identifies if the selection is a single line.
#
# Returns a {Boolean}.
isSingleScreenLine: ->
@getScreenRange().isSingleLine()
clearAutoscroll: ->
@needsAutoscroll = null
# Public: Retrieves the screen range for the selection.
#
# Returns a {Range}.
getScreenRange: ->
@editSession.getMarkerScreenRange(@marker)
# Public: Modifies the screen range for the selection.
#
# screenRange - The new {Range} to use
# options - A hash of options matching those found in {.setBufferRange}
setScreenRange: (screenRange, options) ->
@setBufferRange(@editSession.bufferRangeForScreenRange(screenRange), options)
# Public: Retrieves the buffer range for the selection.
#
# Returns a {Range}.
getBufferRange: ->
@editSession.getMarkerBufferRange(@marker)
# Public: Modifies the buffer range for the selection.
#
# screenRange - The new {Range} to select
# options - A hash of options with the following keys:
# :preserveFolds - if `true`, the fold settings are preserved after the selection moves
# :autoscroll - if `true`, the {EditSession} scrolls to the new selection
setBufferRange: (bufferRange, options={}) ->
bufferRange = Range.fromObject(bufferRange)
@needsAutoscroll = options.autoscroll
@@ -62,6 +96,9 @@ class Selection
@cursor.needsAutoscroll = false if options.autoscroll?
@editSession.setMarkerBufferRange(@marker, bufferRange, options)
# Public: Retrieves the starting and ending buffer rows the selection is highlighting.
#
# Returns an {Array} of two {Number}s: the starting row, and the ending row.
getBufferRowRange: ->
range = @getBufferRange()
start = range.start.row
@@ -69,16 +106,24 @@ class Selection
end = Math.max(start, end - 1) if range.end.column == 0
[start, end]
# Internal:
screenRangeChanged: ->
screenRange = @getScreenRange()
@trigger 'screen-range-changed', screenRange
# Public: Retrieves the text in the selection.
#
# Returns a {String}.
getText: ->
@editSession.buffer.getTextInRange(@getBufferRange())
# Public: Clears the selection, moving the marker to move to the head.
clear: ->
@editSession.clearMarkerTail(@marker)
# Public: Modifies the selection to mark the current word.
#
# Returns a {Range}.
selectWord: ->
options = {}
options.wordRegex = /[\t ]*/ if @cursor.isSurroundedByWhitespace()
@@ -90,6 +135,9 @@ class Selection
expandOverWord: ->
@setBufferRange(@getBufferRange().union(@cursor.getCurrentWordBufferRange()))
# Public: Selects an entire line in the {Buffer}.
#
# row - The line {Number} to select (default: the row of the cursor)
selectLine: (row=@cursor.getBufferPosition().row) ->
range = @editSession.bufferRangeForBufferRow(row, includeNewline: true)
@setBufferRange(range)
@@ -101,6 +149,9 @@ class Selection
range = @getBufferRange().union(@cursor.getCurrentLineBufferRange(includeNewline: true))
@setBufferRange(range)
# Public: Selects the text from the current cursor position to a given screen position.
#
# position - An instance of {Point}, with a given `row` and `column`.
selectToScreenPosition: (position) ->
@modifySelection =>
if @initialScreenRange
@@ -116,45 +167,61 @@ class Selection
else if @wordwise
@expandOverWord()
# Public: Selects the text from the current cursor position to a given buffer position.
#
# position - An instance of {Point}, with a given `row` and `column`.
selectToBufferPosition: (position) ->
@modifySelection => @cursor.setBufferPosition(position)
# Public: Selects the text one position right of the cursor.
selectRight: ->
@modifySelection => @cursor.moveRight()
# Public: Selects the text one position left of the cursor.
selectLeft: ->
@modifySelection => @cursor.moveLeft()
# Public: Selects all the text one position above the cursor.
selectUp: ->
@modifySelection => @cursor.moveUp()
# Public: Selects all the text one position below the cursor.
selectDown: ->
@modifySelection => @cursor.moveDown()
# Public: Selects all the text from the current cursor position to the top of the buffer.
selectToTop: ->
@modifySelection => @cursor.moveToTop()
# Public: Selects all the text from the current cursor position to the bottom of the buffer.
selectToBottom: ->
@modifySelection => @cursor.moveToBottom()
# Public: Selects all the text in the buffer.
selectAll: ->
@setBufferRange(@editSession.buffer.getRange(), autoscroll: false)
# Public: Selects all the text from the current cursor position to the beginning of the line.
selectToBeginningOfLine: ->
@modifySelection => @cursor.moveToBeginningOfLine()
# Public: Selects all the text from the current cursor position to the end of the line.
selectToEndOfLine: ->
@modifySelection => @cursor.moveToEndOfLine()
# Public: Selects all the text from the current cursor position to the beginning of the word.
selectToBeginningOfWord: ->
@modifySelection => @cursor.moveToBeginningOfWord()
# Public: Selects all the text from the current cursor position to the end of the word.
selectToEndOfWord: ->
@modifySelection => @cursor.moveToEndOfWord()
# Public: Selects all the text from the current cursor position to the beginning of the next word.
selectToBeginningOfNextWord: ->
@modifySelection => @cursor.moveToBeginningOfNextWord()
# Public: Moves the selection down one row.
addSelectionBelow: ->
range = (@goalBufferRange ? @getBufferRange()).copy()
nextRow = range.end.row + 1
@@ -172,6 +239,7 @@ class Selection
@editSession.addSelectionForBufferRange(range, goalBufferRange: range, suppressMerge: true)
break
# Public: Moves the selection up one row.
addSelectionAbove: ->
range = (@goalBufferRange ? @getBufferRange()).copy()
previousRow = range.end.row - 1
@@ -189,6 +257,13 @@ class Selection
@editSession.addSelectionForBufferRange(range, goalBufferRange: range, suppressMerge: true)
break
# Public: Replaces text at the current selection.
#
# text - A {String} representing the text to add
# options - A hash containing the following options:
# :normalizeIndent - TODO
# :select - if `true`, selects the newly added text
# :autoIndent - if `true`, indents the newly added text appropriately
insertText: (text, options={}) ->
oldBufferRange = @getBufferRange()
@editSession.destroyFoldsContainingBufferRow(oldBufferRange.end.row)
@@ -210,6 +285,10 @@ class Selection
newBufferRange
# Public: Indents the selection.
#
# options - A hash with one key, `autoIndent`. If `true`, the indentation is
# performed appropriately. Otherwise, {EditSession#getTabText} is used
indent: ({ autoIndent }={})->
{ row, column } = @cursor.getBufferPosition()
@@ -225,6 +304,7 @@ class Selection
else
@indentSelectedRows()
# Public: If the selection spans multiple rows, indents all of them.
indentSelectedRows: ->
[start, end] = @getBufferRowRange()
for row in [start..end]
@@ -270,6 +350,7 @@ class Selection
desiredIndentString = @editSession.buildIndentString(desiredIndentLevel)
line.replace(/^[\t ]*/, desiredIndentString)
# Public: Performs a backspace, removing the character found behind the selection.
backspace: ->
if @isEmpty() and not @editSession.isFoldedAtScreenRow(@cursor.getScreenRow())
if @cursor.isAtBeginningOfLine() and @editSession.isFoldedAtScreenRow(@cursor.getScreenRow() - 1)
@@ -279,10 +360,12 @@ class Selection
@deleteSelectedText()
# Public: Performs a backspace to the beginning of the current word, removing characters found there.
backspaceToBeginningOfWord: ->
@selectToBeginningOfWord() if @isEmpty()
@deleteSelectedText()
# Public: Performs a backspace to the beginning of the current line, removing characters found there.
backspaceToBeginningOfLine: ->
if @isEmpty() and @cursor.isAtBeginningOfLine()
@selectLeft()
@@ -290,6 +373,7 @@ class Selection
@selectToBeginningOfLine()
@deleteSelectedText()
# Public: Performs a delete, removing the character found ahead of the cursor position.
delete: ->
if @isEmpty()
if @cursor.isAtEndOfLine() and fold = @editSession.largestFoldStartingAtScreenRow(@cursor.getScreenRow() + 1)
@@ -298,10 +382,12 @@ class Selection
@selectRight()
@deleteSelectedText()
# Public: Performs a delete to the end of the current word, removing characters found there.
deleteToEndOfWord: ->
@selectToEndOfWord() if @isEmpty()
@deleteSelectedText()
# Public: Deletes the selected text.
deleteSelectedText: ->
bufferRange = @getBufferRange()
if fold = @editSession.largestFoldContainingBufferRow(bufferRange.end.row)
@@ -311,6 +397,7 @@ class Selection
@editSession.buffer.delete(bufferRange) unless bufferRange.isEmpty()
@cursor?.setBufferPosition(bufferRange.start)
# Public: Deletes the line.
deleteLine: ->
if @isEmpty()
start = @cursor.getScreenRow()
@@ -327,6 +414,9 @@ class Selection
end--
@editSession.buffer.deleteRows(start, end)
# Public: Joins the current line with the one below it.
#
# If there selection spans more than one line, all the lines are joined together.
joinLine: ->
selectedRange = @getBufferRange()
if selectedRange.isEmpty()
@@ -364,17 +454,29 @@ class Selection
[start, end] = @getBufferRowRange()
@editSession.autoIndentBufferRows(start, end)
# Public: Wraps the selected lines in comments.
#
# Returns an {Array} of the commented {Ranges}.
toggleLineComments: ->
@editSession.toggleLineCommentsForBufferRows(@getBufferRowRange()...)
# Public: Performs a cut operation on the selection, until the end of the line.
#
# maintainPasteboard - A {Boolean} indicating TODO
cutToEndOfLine: (maintainPasteboard) ->
@selectToEndOfLine() if @isEmpty()
@cut(maintainPasteboard)
# Public: Performs a cut operation on the selection.
#
# maintainPasteboard - A {Boolean} indicating TODO
cut: (maintainPasteboard=false) ->
@copy(maintainPasteboard)
@delete()
# Public: Performs a copy operation on the selection.
#
# maintainPasteboard - A {Boolean} indicating TODO
copy: (maintainPasteboard=false) ->
return if @isEmpty()
text = @editSession.buffer.getTextInRange(@getBufferRange())
@@ -386,6 +488,7 @@ class Selection
pasteboard.write(text, metadata)
# Public: Folds the selection.
fold: ->
range = @getBufferRange()
@editSession.createFold(range.start.row, range.end.row)
@@ -406,12 +509,26 @@ class Selection
placeTail: ->
@editSession.placeMarkerTail(@marker)
# Public: Identifies if a selection intersects with a given buffer range.
#
# bufferRange - A {Range} to check against
#
# Returns a {Boolean}.
intersectsBufferRange: (bufferRange) ->
@getBufferRange().intersectsWith(bufferRange)
# Public: Identifies if a selection intersects with another selection.
#
# otherSelection - A `Selection` to check against
#
# Returns a {Boolean}.
intersectsWith: (otherSelection) ->
@getBufferRange().intersectsWith(otherSelection.getBufferRange())
# Public: Merges two selections together.
#
# otherSelection - A `Selection` to merge with
# options - A hash of options matching those found in {.setBufferRange}
merge: (otherSelection, options) ->
@setBufferRange(@getBufferRange().union(otherSelection.getBufferRange()), options)
if @goalBufferRange and otherSelection.goalBufferRange
+14
Ver Arquivo
@@ -6,6 +6,10 @@ fsUtils = require 'fs-utils'
EventEmitter = require 'event-emitter'
NullGrammar = require 'null-grammar'
###
# Internal #
###
module.exports =
class Syntax
registerDeserializer(this)
@@ -19,6 +23,7 @@ class Syntax
@nullGrammar = new NullGrammar
@grammars = [@nullGrammar]
@grammarsByScopeName = {}
@injectionGrammars = []
@grammarOverridesByPath = {}
@scopedPropertiesIndex = 0
@scopedProperties = []
@@ -27,13 +32,22 @@ class Syntax
{ deserializer: @constructor.name, @grammarOverridesByPath }
addGrammar: (grammar) ->
previousGrammars = new Array(@grammars...)
@grammars.push(grammar)
@grammarsByScopeName[grammar.scopeName] = grammar
@injectionGrammars.push(grammar) if grammar.injectionSelector?
@grammarUpdated(grammar.scopeName)
@trigger 'grammar-added', grammar
removeGrammar: (grammar) ->
_.remove(@grammars, grammar)
delete @grammarsByScopeName[grammar.scopeName]
_.remove(@injectionGrammars, grammar)
@grammarUpdated(grammar.scopeName)
grammarUpdated: (scopeName) ->
for grammar in @grammars when grammar.scopeName isnt scopeName
@trigger 'grammar-updated', grammar if grammar.grammarUpdated(scopeName)
setGrammarOverrideForPath: (path, scopeName) ->
@grammarOverridesByPath[path] = scopeName
+288 -8
Ver Arquivo
@@ -8,6 +8,10 @@ UndoManager = require 'undo-manager'
BufferChangeOperation = require 'buffer-change-operation'
BufferMarker = require 'buffer-marker'
# Public: Represents the contents of a file.
#
# The `Buffer` is often associated with a {File}. However, this is not always
# the case, as a `Buffer` could be an unsaved chunk of text.
module.exports =
class Buffer
@idCounter = 1
@@ -25,9 +29,10 @@ class Buffer
invalidMarkers: null
refcount: 0
@deserialize: ({path, text}) ->
project.bufferForPath(path, text)
# Public: Creates a new buffer.
#
# path - A {String} representing the file path
# initialText - A {String} setting the starting text
constructor: (path, initialText) ->
@id = @constructor.idCounter++
@nextMarkerId = 1
@@ -50,6 +55,10 @@ class Buffer
@undoManager = new UndoManager(this)
###
# Internal #
###
destroy: ->
throw new Error("Destroying buffer twice with path '#{@getPath()}'") if @destroyed
@file?.off()
@@ -70,7 +79,8 @@ class Buffer
path: @getPath()
text: @getText() if @isModified()
hasMultipleEditors: -> @refcount > 1
@deserialize: ({path, text}) ->
project.bufferForPath(path, text)
subscribeToFile: ->
@file.on "contents-changed", =>
@@ -87,7 +97,21 @@ class Buffer
@file.on "moved", =>
@trigger "path-changed", this
###
# Public #
###
# Public: Identifies if the buffer belongs to multiple editors.
#
# For example, if the {Editor} was split.
#
# Returns a {Boolean}.
hasMultipleEditors: -> @refcount > 1
# Public: Reloads a file in the {EditSession}.
#
# Essentially, this performs a force read of the file.
reload: ->
@trigger 'will-reload'
@updateCachedDiskContents()
@@ -95,15 +119,27 @@ class Buffer
@triggerModifiedStatusChanged(false)
@trigger 'reloaded'
# Public: Rereads the contents of the file, and stores them in the cache.
#
# Essentially, this performs a force read of the file on disk.
updateCachedDiskContents: ->
@cachedDiskContents = @file.read()
# Public: Gets the file's basename--that is, the file without any directory information.
#
# Returns a {String}.
getBaseName: ->
@file?.getBaseName()
# Public: Retrieves the path for the file.
#
# Returns a {String}.
getPath: ->
@file?.getPath()
# Public: Sets the path for the file.
#
# path - A {String} representing the new file path
setPath: (path) ->
return if path == @getPath()
@@ -114,21 +150,38 @@ class Buffer
@trigger "path-changed", this
# Public: Retrieves the current buffer's file extension.
#
# Returns a {String}.
getExtension: ->
if @getPath()
@getPath().split('/').pop().split('.').pop()
else
null
# Public: Retrieves the cached buffer contents.
#
# Returns a {String}.
getText: ->
@cachedMemoryContents ?= @getTextInRange(@getRange())
# Public: Replaces the current buffer contents.
#
# text - A {String} containing the new buffer contents.
setText: (text) ->
@change(@getRange(), text, normalizeLineEndings: false)
# Public: Gets the range of the buffer contents.
#
# Returns a new {Range}, from `[0, 0]` to the end of the buffer.
getRange: ->
new Range([0, 0], [@getLastRow(), @getLastLine().length])
# Public: Given a range, returns the lines of text within it.
#
# range - A {Range} object specifying your points of interest
#
# Returns a {String} of the combined lines.
getTextInRange: (range) ->
range = @clipRange(range)
if range.start.row == range.end.row
@@ -144,9 +197,17 @@ class Buffer
return multipleLines.join ''
# Public: Gets all the lines in a file.
#
# Returns an {Array} of {String}s.
getLines: ->
@lines
# Public: Given a row, returns the line of text.
#
# row - A {Number} indicating the row.
#
# Returns a {String}.
lineForRow: (row) ->
@lines[row]
@@ -156,27 +217,51 @@ class Buffer
suggestedLineEndingForRow: (row) ->
@lineEndingForRow(row) ? @lineEndingForRow(row - 1)
# Public: Given a row, returns the length of the line of text.
#
# row - A {Number} indicating the row.
#
# Returns a {Number}.
lineLengthForRow: (row) ->
@lines[row].length
lineEndingLengthForRow: (row) ->
(@lineEndingForRow(row) ? '').length
# Public: Given a buffer row, this retrieves the range for that line.
#
# row - A {Number} identifying the row
# options - A hash with one key, `includeNewline`, which specifies whether you
# want to include the trailing newline
#
# Returns a {Range}.
rangeForRow: (row, { includeNewline } = {}) ->
if includeNewline and row < @getLastRow()
new Range([row, 0], [row + 1, 0])
else
new Range([row, 0], [row, @lineLengthForRow(row)])
# Public: Gets the number of lines in a file.
#
# Returns a {Number}.
getLineCount: ->
@getLines().length
# Public: Gets the row number of the last line.
#
# Returns a {Number}.
getLastRow: ->
@getLines().length - 1
# Public: Finds the last line in the current buffer.
#
# Returns a {String}.
getLastLine: ->
@lineForRow(@getLastRow())
# Public: Finds the last point in the current buffer.
#
# Returns a {Point} representing the last position.
getEofPosition: ->
lastRow = @getLastRow()
new Point(lastRow, @lineLengthForRow(lastRow))
@@ -197,9 +282,16 @@ class Buffer
new Point(row, index)
# Public: Given a row, this deletes it from the buffer.
#
# row - A {Number} representing the row to delete
deleteRow: (row) ->
@deleteRows(row, row)
# Public: Deletes a range of rows from the buffer.
#
# start - A {Number} representing the starting row
# end - A {Number} representing the ending row
deleteRows: (start, end) ->
startPoint = null
endPoint = null
@@ -215,21 +307,39 @@ class Buffer
@delete(new Range(startPoint, endPoint))
# Public: Adds text to the end of the buffer.
#
# text - A {String} of text to add
append: (text) ->
@insert(@getEofPosition(), text)
# Public: Adds text to a specific point in the buffer
#
# point - A {Point} in the buffer to insert into
# text - A {String} of text to add
insert: (point, text) ->
@change(new Range(point, point), text)
# Public: Deletes text from the buffer
#
# range - A {Range} whose text to delete
delete: (range) ->
@change(range, '')
# Internal:
change: (oldRange, newText, options) ->
oldRange = Range.fromObject(oldRange)
operation = new BufferChangeOperation({buffer: this, oldRange, newText, options})
range = @pushOperation(operation)
range
# Public: Given a position, this clips it to a real position.
#
# For example, if `position`'s row exceeds the row count of the buffer,
# or if its column goes beyond a line's length, this "sanitizes" the value
# to a real position.
#
# Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed.
clipPosition: (position) ->
position = Point.fromObject(position)
eofPosition = @getEofPosition()
@@ -240,7 +350,16 @@ class Buffer
column = Math.max(position.column, 0)
column = Math.min(@lineLengthForRow(row), column)
new Point(row, column)
# Public: Given a range, this clips it to a real range.
#
# For example, if `range`'s row exceeds the row count of the buffer,
# or if its column goes beyond a line's length, this "sanitizes" the value
# to a real range.
#
# range - The {Point} to clip
#
# Returns the new, clipped {Point}. Note that this could be the same as `range` if no clipping was performed.
clipRange: (range) ->
range = Range.fromObject(range)
new Range(@clipPosition(range.start), @clipPosition(range.end))
@@ -249,21 +368,33 @@ class Buffer
prefix: @lines[range.start.row][0...range.start.column]
suffix: @lines[range.end.row][range.end.column..]
# Internal:
pushOperation: (operation, editSession) ->
if @undoManager
@undoManager.pushOperation(operation, editSession)
else
operation.do()
# Internal:
transact: (fn) -> @undoManager.transact(fn)
# Public: Undos the last operation.
#
# editSession - The {EditSession} associated with the buffer.
undo: (editSession) -> @undoManager.undo(editSession)
# Public: Redos the last operation.
#
# editSession - The {EditSession} associated with the buffer.
redo: (editSession) -> @undoManager.redo(editSession)
commit: -> @undoManager.commit()
abort: -> @undoManager.abort()
# Public: Saves the buffer.
save: ->
@saveAs(@getPath()) if @isModified()
# Public: Saves the buffer at a specific path.
#
# path - The path to save at.
saveAs: (path) ->
unless path then throw new Error("Can't save buffer with no file path")
@@ -274,22 +405,40 @@ class Buffer
@triggerModifiedStatusChanged(false)
@trigger 'saved'
# Public: Identifies if the buffer was modified.
#
# Returns a {Boolean}.
isModified: ->
if @file
@getText() != @cachedDiskContents
else
not @isEmpty()
# Public: Identifies if a buffer is in a git conflict with `HEAD`.
#
# Returns a {Boolean}.
isInConflict: -> @conflict
# Public: Identifies if a buffer is empty.
#
# Returns a {Boolean}.
isEmpty: -> @lines.length is 1 and @lines[0].length is 0
getMarkers: ->
_.values(@validMarkers)
# Public: Retrieves the quantity of markers in a buffer.
#
# Returns a {Number}.
getMarkerCount: ->
_.size(@validMarkers)
# Public: Constructs a new marker at a given range.
#
# range - The marker {Range} (representing the distance between the head and tail)
# options - Options to pass to the {BufferMarker} constructor
#
# Returns a {Number} representing the new marker's ID.
markRange: (range, options={}) ->
marker = new BufferMarker(_.defaults({
id: (@nextMarkerId++).toString()
@@ -299,9 +448,18 @@ class Buffer
@validMarkers[marker.id] = marker
marker.id
# Public: Constructs a new marker at a given position.
#
# position - The marker {Point}; there won't be a tail
# options - Options to pass to the {BufferMarker} constructor
#
# Returns a {Number} representing the new marker's ID.
markPosition: (position, options) ->
@markRange([position, position], _.defaults({noTail: true}, options))
# Public: Removes the marker with the given id.
#
# id - The {Number} of the ID to remove
destroyMarker: (id) ->
delete @validMarkers[id]
delete @invalidMarkers[id]
@@ -312,39 +470,110 @@ class Buffer
setMarkerPosition: (args...) ->
@setMarkerHeadPosition(args...)
# Public: Retrieves the position of the marker's head.
#
# id - A {Number} representing the marker to check
#
# Returns a {Point}, or `null` if the marker does not exist.
getMarkerHeadPosition: (id) ->
@validMarkers[id]?.getHeadPosition()
# Public: Sets the position of the marker's head.
#
# id - A {Number} representing the marker to change
# position - The new {Point} to place the head
# options - A hash with the following keys:
# :clip - if `true`, the point is [clipped]{Buffer.clipPosition}
# :bufferChanged - if `true`, indicates that the {Buffer} should trigger an event that it's changed
#
# Returns a {Point} representing the new head position.
setMarkerHeadPosition: (id, position, options) ->
@validMarkers[id]?.setHeadPosition(position)
# Public: Retrieves the position of the marker's tail.
#
# id - A {Number} representing the marker to check
#
# Returns a {Point}, or `null` if the marker does not exist.
getMarkerTailPosition: (id) ->
@validMarkers[id]?.getTailPosition()
# Public: Sets the position of the marker's tail.
#
# id - A {Number} representing the marker to change
# position - The new {Point} to place the tail
# options - A hash with the following keys:
# :clip - if `true`, the point is [clipped]{Buffer.clipPosition}
# :bufferChanged - if `true`, indicates that the {Buffer} should trigger an event that it's changed
#
# Returns a {Point} representing the new tail position.
setMarkerTailPosition: (id, position, options) ->
@validMarkers[id]?.setTailPosition(position)
# Public: Retrieves the {Range} between a marker's head and its tail.
#
# id - A {Number} representing the marker to check
#
# Returns a {Range}.
getMarkerRange: (id) ->
@validMarkers[id]?.getRange()
# Public: Sets the marker's range, potentialy modifying both its head and tail.
#
# id - A {Number} representing the marker to change
# range - The new {Range} the marker should cover
# options - A hash of options with the following keys:
# :reverse - if `true`, the marker is reversed; that is, its tail is "above" the head
# :noTail - if `true`, the marker doesn't have a tail
setMarkerRange: (id, range, options) ->
@validMarkers[id]?.setRange(range, options)
# Public: Sets the marker's tail to the same position as the marker's head.
#
# This only works if there isn't already a tail position.
#
# id - A {Number} representing the marker to change
#
# Returns a {Point} representing the new tail position.
placeMarkerTail: (id) ->
@validMarkers[id]?.placeTail()
# Public: Removes the tail from the marker.
#
# id - A {Number} representing the marker to change
clearMarkerTail: (id) ->
@validMarkers[id]?.clearTail()
# Public: Identifies if the ending position of a marker is greater than the starting position.
#
# This can happen when, for example, you highlight text "up" in a {Buffer}.
#
# id - A {Number} representing the marker to check
#
# Returns a {Boolean}.
isMarkerReversed: (id) ->
@validMarkers[id]?.isReversed()
# Public: Identifies if the marker's head position is equal to its tail.
#
# id - A {Number} representing the marker to check
#
# Returns a {Boolean}.
isMarkerRangeEmpty: (id) ->
@validMarkers[id]?.isRangeEmpty()
# Public: Sets a callback to be fired whenever a marker is changed.
#
# id - A {Number} representing the marker to watch
# callback - A {Function} to execute
observeMarker: (id, callback) ->
@validMarkers[id]?.observe(callback)
# Public: Given a buffer position, this finds all markers that contain the position.
#
# bufferPosition - A {Point} to check
#
# Returns an {Array} of {Numbers}, representing marker IDs containing `bufferPosition`.
markersForPosition: (bufferPosition) ->
bufferPosition = Point.fromObject(bufferPosition)
ids = []
@@ -352,6 +581,13 @@ class Buffer
ids.push(id) if marker.containsPoint(bufferPosition)
ids
# Public: Identifies if a character sequence is within a certain range.
#
# regex - The {RegExp} to check
# startIndex - The starting row {Number}
# endIndex - The ending row {Number}
#
# Returns an {Array} of {RegExp}s, representing the matches.
matchesInCharacterRange: (regex, startIndex, endIndex) ->
text = @getText()
matches = []
@@ -375,9 +611,19 @@ class Buffer
matches
# Public: Scans for text in the buffer, calling a function on each match.
#
# regex - A {RegExp} representing the text to find
# iterator - A {Function} that's called on each match
scan: (regex, iterator) ->
@scanInRange(regex, @getRange(), iterator)
# Public: Scans for text in a given range, calling a function on each match.
#
# regex - A {RegExp} representing the text to find
# range - A {Range} in the buffer to search within
# iterator - A {Function} that's called on each match
# reverse - A {Boolean} indicating if the search should be backwards (default: `false`)
scanInRange: (regex, range, iterator, reverse=false) ->
range = @clipRange(range)
global = regex.global
@@ -415,12 +661,28 @@ class Buffer
break unless global and keepLooping
# Public: Scans for text in a given range _backwards_, calling a function on each match.
#
# regex - A {RegExp} representing the text to find
# range - A {Range} in the buffer to search within
# iterator - A {Function} that's called on each match
backwardsScanInRange: (regex, range, iterator) ->
@scanInRange regex, range, iterator, true
# Public: Given a row, identifies if it is blank.
#
# row - A row {Number} to check
#
# Returns a {Boolean}.
isRowBlank: (row) ->
not /\S/.test @lineForRow(row)
# Public: Given a row, this finds the next row above it that's empty.
#
# startRow - A {Number} identifying the row to start checking at
#
# Returns the row {Number} of the first blank row.
# Returns `null` if there's no other blank row.
previousNonBlankRow: (startRow) ->
return null if startRow == 0
@@ -429,6 +691,12 @@ class Buffer
return row unless @isRowBlank(row)
null
# Public: Given a row, this finds the next row that's blank.
#
# startRow - A row {Number} to check
#
# Returns the row {Number} of the next blank row.
# Returns `null` if there's no other blank row.
nextNonBlankRow: (startRow) ->
lastRow = @getLastRow()
if startRow < lastRow
@@ -436,17 +704,32 @@ class Buffer
return row unless @isRowBlank(row)
null
# Public: Identifies if the buffer has soft tabs anywhere.
#
# Returns a {Boolean},
usesSoftTabs: ->
for line in @getLines()
if match = line.match(/^\s/)
return match[0][0] != '\t'
undefined
# Public: Checks out the current `HEAD` revision of the file.
checkoutHead: ->
path = @getPath()
return unless path
git?.checkoutHead(path)
# Public: Checks to see if a file exists.
#
# Returns a {Boolean}.
fileExists: ->
@file? && @file.exists()
###
# Internal #
###
scheduleModifiedEvents: ->
clearTimeout(@stoppedChangingTimeout) if @stoppedChangingTimeout
stoppedChangingCallback = =>
@@ -461,9 +744,6 @@ class Buffer
@previousModifiedStatus = modifiedStatus
@trigger 'modified-status-changed', modifiedStatus
fileExists: ->
@file? && @file.exists()
logLines: (start=0, end=@getLastRow())->
for row in [start..end]
line = @lineForRow(row)
+201 -59
Ver Arquivo
@@ -4,7 +4,13 @@ plist = require 'plist'
Token = require 'token'
{OnigRegExp, OnigScanner} = require 'oniguruma'
nodePath = require 'path'
EventEmitter = require 'event-emitter'
pathSplitRegex = new RegExp("[#{nodePath.sep}.]")
TextMateScopeSelector = require 'text-mate-scope-selector'
###
# Internal #
###
module.exports =
class TextMateGrammar
@@ -22,22 +28,52 @@ class TextMateGrammar
new TextMateGrammar(fsUtils.readObject(path))
name: null
rawPatterns: null
rawRepository: null
fileTypes: null
scopeName: null
repository: null
initialRule: null
firstLineRegex: null
includedGrammarScopes: null
maxTokensPerLine: 100
constructor: ({ @name, @fileTypes, @scopeName, patterns, repository, @foldingStopMarker, firstLineMatch}) ->
@initialRule = new Rule(this, {@scopeName, patterns})
@repository = {}
constructor: ({ @name, @fileTypes, @scopeName, injections, injectionSelector, patterns, repository, @foldingStopMarker, firstLineMatch}) ->
@rawPatterns = patterns
@rawRepository = repository
@injections = new Injections(this, injections)
if injectionSelector?
@injectionSelector = new TextMateScopeSelector(injectionSelector)
@firstLineRegex = new OnigRegExp(firstLineMatch) if firstLineMatch
@fileTypes ?= []
@includedGrammarScopes = []
for name, data of repository
data = {patterns: [data], tempName: name} if data.begin? or data.match?
@repository[name] = new Rule(this, data)
clearRules: ->
@initialRule = null
@repository = null
getInitialRule: ->
@initialRule ?= new Rule(this, {@scopeName, patterns: @rawPatterns})
getRepository: ->
@repository ?= do =>
repository = {}
for name, data of @rawRepository
data = {patterns: [data], tempName: name} if data.begin? or data.match?
repository[name] = new Rule(this, data)
repository
addIncludedGrammarScope: (scope) ->
@includedGrammarScopes.push(scope) unless _.include(@includedGrammarScopes, scope)
grammarUpdated: (scopeName) ->
return false unless _.include(@includedGrammarScopes, scopeName)
@clearRules()
syntax.grammarUpdated(@scopeName)
@trigger 'grammar-updated'
true
getScore: (path, contents) ->
contents = fsUtils.read(path) if not contents? and fsUtils.isFile(path)
@@ -76,7 +112,7 @@ class TextMateGrammar
pathSuffix = pathComponents[-fileTypeComponents.length..-1]
_.isEqual(pathSuffix, fileTypeComponents)
tokenizeLine: (line, ruleStack=[@initialRule], firstLine=false) ->
tokenizeLine: (line, ruleStack=[@getInitialRule()], firstLine=false) ->
originalRuleStack = ruleStack
ruleStack = new Array(ruleStack...) # clone ruleStack
tokens = []
@@ -128,6 +164,47 @@ class TextMateGrammar
getMaxTokensPerLine: ->
@maxTokensPerLine
class Injections
@injections: null
constructor: (grammar, injections={}) ->
@injections = []
@scanners = {}
for selector, values of injections
continue unless values?.patterns?.length > 0
patterns = []
anchored = false
for regex in values.patterns
pattern = new Pattern(grammar, regex)
anchored = true if pattern.anchored
patterns.push(pattern)
@injections.push
anchored: anchored
selector: new TextMateScopeSelector(selector)
patterns: patterns
getScanner: (injection, firstLine, position, anchorPosition) ->
return injection.scanner if injection.scanner?
regexes = _.map injection.patterns, (pattern) ->
pattern.getRegex(firstLine, position, @anchorPosition)
scanner = new OnigScanner(regexes)
scanner.patterns = injection.patterns
scanner.anchored = injection.anchored
injection.scanner = scanner unless scanner.anchored
scanner
getScanners: (ruleStack, firstLine, position, anchorPosition) ->
scanners = []
scopes = scopesFromStack(ruleStack)
for injection in @injections
if injection.selector.matches(scopes)
scanner = @getScanner(injection, firstLine, position, anchorPosition)
scanners.push(scanner)
scanners
_.extend TextMateGrammar.prototype, EventEmitter
class Rule
grammar: null
scopeName: null
@@ -153,41 +230,69 @@ class Rule
clearAnchorPosition: -> @anchorPosition = -1
createScanner: (patterns, firstLine, position) ->
anchored = false
regexes = _.map patterns, (pattern) =>
anchored = true if pattern.anchored
pattern.getRegex(firstLine, position, @anchorPosition)
scanner = new OnigScanner(regexes)
scanner.patterns = patterns
scanner.anchored = anchored
scanner
getScanner: (baseGrammar, position, firstLine) ->
return scanner if scanner = @scannersByBaseGrammarName[baseGrammar.name]
anchored = false
regexes = []
patterns = @getIncludedPatterns(baseGrammar)
patterns.forEach (pattern) =>
if pattern.anchored
anchored = true
regex = pattern.replaceAnchor(firstLine, position, @anchorPosition)
else
regex = pattern.regexSource
regexes.push regex if regex
scanner = @createScanner(patterns, firstLine, position)
@scannersByBaseGrammarName[baseGrammar.name] = scanner unless scanner.anchored
scanner
regexScanner = new OnigScanner(regexes)
regexScanner.patterns = patterns
@scannersByBaseGrammarName[baseGrammar.name] = regexScanner unless anchored
regexScanner
getNextTokens: (ruleStack, line, position, firstLine) ->
scanInjections: (ruleStack, line, position, firstLine) ->
baseGrammar = ruleStack[0].grammar
patterns = @getIncludedPatterns(baseGrammar)
if injections = baseGrammar.injections
scanners = injections.getScanners(ruleStack, position, firstLine, @anchorPosition)
for scanner in scanners
result = scanner.findNextMatch(line, position)
return result if result?
normalizeCaptureIndices: (line, captureIndices) ->
lineLength = line.length
for value, index in captureIndices
if index % 3 isnt 0 and value > lineLength
captureIndices[index] = lineLength
findNextMatch: (ruleStack, line, position, firstLine) ->
lineWithNewline = "#{line}\n"
baseGrammar = ruleStack[0].grammar
results = []
scanner = @getScanner(baseGrammar, position, firstLine)
# Add a `\n` to appease patterns that contain '\n' explicitly
return null unless result = scanner.findNextMatch("#{line}\n", position)
{ index, captureIndices } = result
# Since the `\n' (added above) is not part of the line, truncate captures to the line's actual length
lineLength = line.length
captureIndices = captureIndices.map (value, index) ->
value = lineLength if index % 3 != 0 and value > lineLength
value
if result = scanner.findNextMatch(lineWithNewline, position)
results.push(result)
if result = @scanInjections(ruleStack, lineWithNewline, position, firstLine)
results.push(result)
scopes = scopesFromStack(ruleStack)
for injectionGrammar in _.without(syntax.injectionGrammars, @grammar, baseGrammar)
if injectionGrammar.injectionSelector.matches(scopes)
scanner = injectionGrammar.getInitialRule().getScanner(injectionGrammar, position, firstLine)
if result = scanner.findNextMatch(lineWithNewline, position)
results.push(result)
if results.length > 0
_.min results, (result) =>
@normalizeCaptureIndices(line, result.captureIndices)
result.captureIndices[1]
getNextTokens: (ruleStack, line, position, firstLine) ->
result = @findNextMatch(ruleStack, line, position, firstLine)
return null unless result?
{ index, captureIndices, scanner } = result
[firstCaptureIndex, firstCaptureStart, firstCaptureEnd] = captureIndices
nextTokens = patterns[index].handleMatch(ruleStack, line, captureIndices)
nextTokens = scanner.patterns[index].handleMatch(ruleStack, line, captureIndices)
{ nextTokens, tokensStartPosition: firstCaptureStart, tokensEndPosition: firstCaptureEnd }
getRuleToPush: (line, beginPatternCaptureIndices) ->
@@ -221,8 +326,21 @@ class Pattern
@captures = beginCaptures ? captures
endPattern = new Pattern(@grammar, { match: end, captures: endCaptures ? captures, popRule: true})
@pushRule = new Rule(@grammar, { @scopeName, patterns, endPattern })
if @captures?
for group, capture of @captures
if capture.patterns?.length > 0 and not capture.rule
capture.scopeName = @scopeName
capture.rule = new Rule(@grammar, capture)
@anchored = @hasAnchor()
getRegex: (firstLine, position, anchorPosition) ->
if @anchored
@replaceAnchor(firstLine, position, anchorPosition)
else
@regexSource
hasAnchor: ->
return false unless @regexSource
escape = false
@@ -274,13 +392,14 @@ class Pattern
ruleForInclude: (baseGrammar, name) ->
if name[0] == "#"
@grammar.repository[name[1..]]
@grammar.getRepository()[name[1..]]
else if name == "$self"
@grammar.initialRule
@grammar.getInitialRule()
else if name == "$base"
baseGrammar.initialRule
baseGrammar.getInitialRule()
else
syntax.grammarForScopeName(name)?.initialRule
@grammar.addIncludedGrammarScope(name)
syntax.grammarForScopeName(name)?.getInitialRule()
getIncludedPatterns: (baseGrammar, included) ->
if @include
@@ -291,10 +410,17 @@ class Pattern
handleMatch: (stack, line, captureIndices) ->
scopes = scopesFromStack(stack)
scopes.push(@scopeName) if @scopeName and not @popRule
if @scopeName and not @popRule
patternScope = @scopeName.replace /\$\d+/, (match) ->
captureIndex = parseInt(match.substring(1)) * 3
if captureIndices[captureIndex]?
line.substring(captureIndices[captureIndex + 1], captureIndices[captureIndex + 2])
else
match
scopes.push(patternScope)
if @captures
tokens = @getTokensForCaptureIndices(line, _.clone(captureIndices), scopes)
tokens = @getTokensForCaptureIndices(line, _.clone(captureIndices), scopes, stack)
else
[start, end] = captureIndices[1..2]
zeroLengthMatch = end == start
@@ -311,41 +437,57 @@ class Pattern
tokens
getTokensForCaptureIndices: (line, captureIndices, scopes) ->
getTokensForCaptureRule: (rule, line, captureStart, captureEnd, scopes, stack) ->
captureText = line.substring(captureStart, captureEnd)
{tokens} = rule.grammar.tokenizeLine(captureText, [stack..., rule])
tokens
getTokensForCaptureIndices: (line, captureIndices, scopes, stack) ->
[parentCaptureIndex, parentCaptureStart, parentCaptureEnd] = shiftCapture(captureIndices)
tokens = []
if scope = @captures[parentCaptureIndex]?.name
scopes = scopes.concat(scope)
previousChildCaptureEnd = parentCaptureStart
while captureIndices.length and captureIndices[1] < parentCaptureEnd
[childCaptureIndex, childCaptureStart, childCaptureEnd] = captureIndices
emptyCapture = childCaptureEnd - childCaptureStart == 0
captureHasNoScope = not @captures[childCaptureIndex]
if emptyCapture or captureHasNoScope
if captureRule = @captures[parentCaptureIndex]?.rule
captureTokens = @getTokensForCaptureRule(captureRule, line, parentCaptureStart, parentCaptureEnd, scopes, stack)
tokens.push(captureTokens...)
# Consume child captures
while captureIndices.length and captureIndices[1] < parentCaptureEnd
shiftCapture(captureIndices)
continue
else
previousChildCaptureEnd = parentCaptureStart
while captureIndices.length and captureIndices[1] < parentCaptureEnd
[childCaptureIndex, childCaptureStart, childCaptureEnd] = captureIndices
if childCaptureStart > previousChildCaptureEnd
emptyCapture = childCaptureEnd - childCaptureStart == 0
captureHasNoScope = not @captures[childCaptureIndex]
if emptyCapture or captureHasNoScope
shiftCapture(captureIndices)
continue
if childCaptureStart > previousChildCaptureEnd
tokens.push(new Token(
value: line[previousChildCaptureEnd...childCaptureStart]
scopes: scopes
))
captureTokens = @getTokensForCaptureIndices(line, captureIndices, scopes, stack)
tokens.push(captureTokens...)
previousChildCaptureEnd = childCaptureEnd
if parentCaptureEnd > previousChildCaptureEnd
tokens.push(new Token(
value: line[previousChildCaptureEnd...childCaptureStart]
value: line[previousChildCaptureEnd...parentCaptureEnd]
scopes: scopes
))
captureTokens = @getTokensForCaptureIndices(line, captureIndices, scopes)
tokens.push(captureTokens...)
previousChildCaptureEnd = childCaptureEnd
if parentCaptureEnd > previousChildCaptureEnd
tokens.push(new Token(
value: line[previousChildCaptureEnd...parentCaptureEnd]
scopes: scopes
))
tokens
###
# Internal #
###
shiftCapture = (captureIndices) ->
[captureIndices.shift(), captureIndices.shift(), captureIndices.shift()]
+66 -18
Ver Arquivo
@@ -5,6 +5,10 @@ _ = require 'underscore'
TextMateGrammar = require 'text-mate-grammar'
async = require 'async'
###
# Internal #
###
module.exports =
class TextMatePackage extends Package
@testName: (packageName) ->
@@ -20,13 +24,14 @@ class TextMatePackage extends Package
@preferencesPath = fsUtils.join(@path, "Preferences")
@syntaxesPath = fsUtils.join(@path, "Syntaxes")
@grammars = []
@scopedProperties = []
load: ({sync}={}) ->
if sync
@loadGrammarsSync()
@loadScopedPropertiesSync()
else
TextMatePackage.getLoadQueue().push(this)
@loadScopedProperties()
activate: ->
syntax.addGrammar(grammar) for grammar in @grammars
@@ -41,10 +46,21 @@ class TextMatePackage extends Package
loadGrammars: (done) ->
fsUtils.isDirectoryAsync @syntaxesPath, (isDirectory) =>
if isDirectory
fsUtils.listAsync @syntaxesPath, @legalGrammarExtensions, (err, paths) =>
return console.log("Error loading grammars of TextMate package '#{@path}':", err.stack, err) if err
async.eachSeries paths, @loadGrammarAtPath, done
return done() unless isDirectory
fsUtils.listAsync @syntaxesPath, @legalGrammarExtensions, (error, paths) =>
if error?
console.log("Error loading grammars of TextMate package '#{@path}':", error.stack, error)
done()
return
async.waterfall [
(next) =>
async.eachSeries paths, @loadGrammarAtPath, next
(next) =>
@loadScopedProperties()
next()
], done
loadGrammarAtPath: (path, done) =>
TextMateGrammar.load path, (err, grammar) =>
@@ -53,7 +69,7 @@ class TextMatePackage extends Package
done()
loadGrammarsSync: ->
for path in fsUtils.list(@syntaxesPath, @legalGrammarExtensions) ? []
for path in fsUtils.list(@syntaxesPath, @legalGrammarExtensions)
@addGrammar(TextMateGrammar.loadSync(path))
addGrammar: (grammar) ->
@@ -62,28 +78,60 @@ class TextMatePackage extends Package
getGrammars: -> @grammars
loadScopedProperties: ->
@scopedProperties = []
loadScopedPropertiesSync: ->
for grammar in @getGrammars()
if properties = @propertiesFromTextMateSettings(grammar)
selector = syntax.cssSelectorFromScopeSelector(grammar.scopeName)
@scopedProperties.push({selector, properties})
for {scope, settings} in @getTextMatePreferenceObjects()
for path in fsUtils.list(@preferencesPath)
{scope, settings} = fsUtils.readObject(path)
if properties = @propertiesFromTextMateSettings(settings)
selector = syntax.cssSelectorFromScopeSelector(scope) if scope?
@scopedProperties.push({selector, properties})
getTextMatePreferenceObjects: ->
for {selector, properties} in @scopedProperties
syntax.addProperties(@path, selector, properties)
loadScopedProperties: ->
scopedProperties = []
for grammar in @getGrammars()
if properties = @propertiesFromTextMateSettings(grammar)
selector = syntax.cssSelectorFromScopeSelector(grammar.scopeName)
scopedProperties.push({selector, properties})
preferenceObjects = []
if fsUtils.exists(@preferencesPath)
for preferencePath in fsUtils.list(@preferencesPath)
try
preferenceObjects.push(fsUtils.readObject(preferencePath))
catch e
console.warn "Failed to parse preference at path '#{preferencePath}'", e.stack
preferenceObjects
done = =>
for {scope, settings} in preferenceObjects
if properties = @propertiesFromTextMateSettings(settings)
selector = syntax.cssSelectorFromScopeSelector(scope) if scope?
scopedProperties.push({selector, properties})
@scopedProperties = scopedProperties
if atom.isPackageActive(@path)
for {selector, properties} in @scopedProperties
syntax.addProperties(@path, selector, properties)
@loadTextMatePreferenceObjects(preferenceObjects, done)
loadTextMatePreferenceObjects: (preferenceObjects, done) ->
fsUtils.isDirectoryAsync @preferencesPath, (isDirectory) =>
return done() unless isDirectory
fsUtils.listAsync @preferencesPath, (error, paths) =>
if error?
console.log("Error loading preferences of TextMate package '#{@path}':", error.stack, error)
done()
return
loadPreferencesAtPath = (path, done) ->
fsUtils.readObjectAsync path, (error, preferences) =>
if error?
console.warn("Failed to parse preference at path '#{path}'", error.stack, error)
else
preferenceObjects.push(preferences)
done()
async.eachSeries paths, loadPreferencesAtPath, done
propertiesFromTextMateSettings: (textMateSettings) ->
if textMateSettings.shellVariables
@@ -0,0 +1,82 @@
###
# Internal #
###
class SegmentMatcher
constructor: (segment) ->
@segment = segment.join('')
matches: (scope) ->
scope is @segment
class TrueMatcher
constructor: ->
matches: ->
true
class ScopeMatcher
constructor: (first, others) ->
@segments = [first]
@segments.push(segment[1]) for segment in others
matches: (scope) ->
scopeSegments = scope.split('.')
return false if scopeSegments.length < @segments.length
for segment, index in @segments
return false unless segment.matches(scopeSegments[index])
true
class PathMatcher
constructor: (first, others) ->
@matchers = [first]
@matchers.push(matcher[1]) for matcher in others
matches: (scopes) ->
index = 0
matcher = @matchers[index]
for scope in scopes
matcher = @matchers[++index] if matcher.matches(scope)
return true unless matcher?
false
class OrMatcher
constructor: (@left, @right) ->
matches: (scopes) ->
@left.matches(scopes) or @right.matches(scopes)
class AndMatcher
constructor: (@left, @right) ->
matches: (scopes) ->
@left.matches(scopes) and @right.matches(scopes)
class NegateMatcher
constructor: (@left, @right) ->
matches: (scopes) ->
@left.matches(scopes) and not @right.matches(scopes)
class CompositeMatcher
constructor: (left, operator, right) ->
switch operator
when '|' then @matcher = new OrMatcher(left, right)
when '&' then @matcher = new AndMatcher(left, right)
when '-' then @matcher = new NegateMatcher(left, right)
matches: (scopes) ->
@matcher.matches(scopes)
module.exports = {
AndMatcher
CompositeMatcher
NegateMatcher
OrMatcher
PathMatcher
ScopeMatcher
SegmentMatcher
TrueMatcher
}
@@ -0,0 +1,50 @@
{
var matchers = require('text-mate-scope-selector-matchers');
}
start = _ selector:(selector) _ {
return selector;
}
segment
= _ segment:[a-zA-Z0-9]+ _ {
return new matchers.SegmentMatcher(segment);
}
/ _ scopeName:[\*] _ {
return new matchers.TrueMatcher();
}
scope
= first:segment others:("." segment)* {
return new matchers.ScopeMatcher(first, others);
}
path
= first:scope others:(_ scope)* {
return new matchers.PathMatcher(first, others);
}
expression
= path
/ "(" _ selector:selector _ ")" {
return selector;
}
composite
= left:expression _ operator:[|&-] _ right:composite {
return new matchers.CompositeMatcher(left, operator, right);
}
/ expression
selector
= left:composite _ "," _ right:selector {
return new matchers.OrMatcher(left, right);
}
/ composite
_
= [ \t]*
+30
Ver Arquivo
@@ -0,0 +1,30 @@
PEG = require 'pegjs'
fsUtils = require 'fs-utils'
# Public: Test a stack of scopes to see if they match a scope selector.
module.exports =
class TextMateScopeSelector
@parser: null
@createParser: ->
unless TextMateScopeSelector.parser?
patternPath = require.resolve('text-mate-scope-selector-pattern.pegjs')
TextMateScopeSelector.parser = PEG.buildParser(fsUtils.read(patternPath))
TextMateScopeSelector.parser
source: null
matcher: null
# Public: Create a new scope selector.
#
# source - A {String} to parse as a scope selector.
constructor: (@source) ->
@matcher = TextMateScopeSelector.createParser().parse(@source)
# Public: Check if this scope selector matches the scopes.
#
# scopes - An {Array} of {String}s.
#
# Return a {Boolean}.
matches: (scopes) ->
@matcher.matches(scopes)
+4
Ver Arquivo
@@ -3,6 +3,10 @@ fsUtils = require 'fs-utils'
plist = require 'plist'
Theme = require 'theme'
###
# Internal #
###
module.exports =
class TextMateTheme extends Theme
@testPath: (path) ->
+4
Ver Arquivo
@@ -1,5 +1,9 @@
fsUtils = require 'fs-utils'
###
# Internal #
###
module.exports =
class Theme
@stylesheets: null
+31 -4
Ver Arquivo
@@ -1,10 +1,15 @@
_ = require 'underscore'
ScreenLine = require 'screen-line'
EventEmitter = require 'event-emitter'
Subscriber = require 'subscriber'
Token = require 'token'
Range = require 'range'
Point = require 'point'
###
# Internal #
###
module.exports =
class TokenizedBuffer
@idCounter: 1
@@ -23,7 +28,16 @@ class TokenizedBuffer
@id = @constructor.idCounter++
@resetScreenLines()
@buffer.on "changed.tokenized-buffer#{@id}", (e) => @handleBufferChange(e)
@languageMode.on 'grammar-changed', => @resetScreenLines()
@languageMode.on 'grammar-changed grammar-updated', => @resetScreenLines()
@subscribe syntax, 'grammar-updated grammar-added', (grammar) =>
if grammar.injectionSelector? and @hasTokenForSelector(grammar.injectionSelector)
@resetScreenLines()
hasTokenForSelector: (selector) ->
for {tokens} in @screenLines
for token in tokens
return true if selector.matches(token.scopes)
false
resetScreenLines: ->
@screenLines = @buildPlaceholderScreenLinesForRows(0, @buffer.getLastRow())
@@ -33,9 +47,15 @@ class TokenizedBuffer
setVisible: (@visible) ->
@tokenizeInBackground() if @visible
# Public: Retrieves the current tab length.
#
# Returns a {Number}.
getTabLength: ->
@tabLength
# Public: Specifies the tab length.
#
# tabLength - A {Number} that defines the new tab length.
setTabLength: (@tabLength) ->
lastRow = @buffer.getLastRow()
@screenLines = @buildPlaceholderScreenLinesForRows(0, lastRow)
@@ -103,7 +123,8 @@ class TokenizedBuffer
@updateInvalidRows(start, end, delta)
previousEndStack = @stackForRow(end) # used in spill detection below
@screenLines[start..end] = @buildScreenLinesForRows(start, end + delta, @stackForRow(start - 1))
newScreenLines = @buildScreenLinesForRows(start, end + delta, @stackForRow(start - 1))
_.spliceWithArray(@screenLines, start, end - start + 1, newScreenLines)
newEndStack = @stackForRow(end + delta)
if newEndStack and not _.isEqual(newEndStack, previousEndStack)
@@ -152,9 +173,11 @@ class TokenizedBuffer
@screenLines[row]?.ruleStack
scopesForPosition: (position) ->
@tokenForPosition(position).scopes
tokenForPosition: (position) ->
position = Point.fromObject(position)
token = @screenLines[position.row].tokenAtBufferColumn(position.column)
token.scopes
@screenLines[position.row].tokenAtBufferColumn(position.column)
destroy: ->
@buffer.off ".tokenized-buffer#{@id}"
@@ -219,6 +242,9 @@ class TokenizedBuffer
stop()
position
# Public: Gets the row number of the last line.
#
# Returns a {Number}.
getLastRow: ->
@buffer.getLastRow()
@@ -234,3 +260,4 @@ class TokenizedBuffer
lines.join('\n')
_.extend(TokenizedBuffer.prototype, EventEmitter)
_.extend(TokenizedBuffer.prototype, Subscriber)
+1 -1
Ver Arquivo
@@ -1,7 +1,7 @@
_ = require 'underscore'
# Internal: The object in charge of managing redo and undo operations.
module.exports =
class UndoManager
undoHistory: null
redoHistory: null
+42 -21
Ver Arquivo
@@ -2,7 +2,7 @@ fs = require 'fs'
fsUtils = require 'fs-utils'
$ = require 'jquery'
_ = require 'underscore'
{less} = require 'less'
less = require 'less'
require 'jquery-extensions'
require 'underscore-extensions'
require 'space-pen-extensions'
@@ -10,6 +10,10 @@ require 'space-pen-extensions'
deserializers = {}
deferredDeserializers = {}
###
# Internal #
###
# This method is called in any window needing a general environment, including specs
window.setUpEnvironment = ->
Config = require 'config'
@@ -25,12 +29,7 @@ window.setUpEnvironment = ->
$(document).on 'keydown', keymap.handleKeyEvent
keymap.bindDefaultKeys()
requireStylesheet 'reset'
requireStylesheet 'atom'
requireStylesheet 'overlay'
requireStylesheet 'popover-list'
requireStylesheet 'notification'
requireStylesheet 'markdown'
if nativeStylesheetPath = fsUtils.resolveOnLoadPath(process.platform, ['css', 'less'])
requireStylesheet(nativeStylesheetPath)
@@ -73,13 +72,21 @@ window.shutdown = ->
window.project = null
window.git = null
window.installAtomCommand = (commandPath) ->
return if fsUtils.exists(commandPath)
window.installAtomCommand = (commandPath, done) ->
fs.exists commandPath, (exists) ->
return if exists
bundledCommandPath = fsUtils.resolve(window.resourcePath, 'atom.sh')
if bundledCommandPath?
fsUtils.write(commandPath, fsUtils.read(bundledCommandPath))
fs.chmod(commandPath, 0o755, commandPath)
bundledCommandPath = fsUtils.resolve(window.resourcePath, 'atom.sh')
if bundledCommandPath?
fs.readFile bundledCommandPath, (error, data) ->
if error?
console.warn "Failed to install `atom` binary", error
else
fsUtils.writeAsync commandPath, data, (error) ->
if error?
console.warn "Failed to install `atom` binary", error
else
fs.chmod(commandPath, 0o755, commandPath)
window.handleWindowEvents = ->
$(window).command 'window:toggle-full-screen', => atom.toggleFullScreen()
@@ -140,13 +147,28 @@ window.requireStylesheet = (path) ->
throw new Error("Could not find a file at path '#{path}'")
window.loadStylesheet = (path) ->
content = fsUtils.read(path)
if fsUtils.extension(path) == '.less'
(new less.Parser).parse content, (e, tree) ->
throw new Error(e.message, path, e.line) if e
content = tree.toCSS()
loadLessStylesheet(path)
else
fsUtils.read(path)
content
window.loadLessStylesheet = (path) ->
parser = new less.Parser
syncImport: true
paths: config.lessSearchPaths
filename: path
try
content = null
parser.parse fsUtils.read(path), (e, tree) ->
throw e if e?
content = tree.toCSS()
content
catch e
console.error """
Error compiling less stylesheet: #{path}
Line number: #{e.line}
#{e.message}
"""
window.removeStylesheet = (path) ->
unless fullPath = window.resolveStylesheet(path)
@@ -164,13 +186,11 @@ window.reload = ->
timesReloaded = process.global.timesReloaded ? 0
++timesReloaded
restartValue = if window.location.search.indexOf('spec-bootstrap') == -1 then 10 else 3
if timesReloaded > restartValue
if timesReloaded > 3
atom.restartRendererProcess()
else
$native.reload()
process.global.timesReloaded = timesReloaded
$native.reload()
window.onerror = ->
atom.showDevTools()
@@ -213,5 +233,6 @@ window.profile = (description, fn) ->
console.profileEnd(description)
value
# Public: Shows a dialog asking if the window was _really_ meant to be closed.
confirmClose = ->
rootView.confirmClose().done -> window.close()
@@ -1,3 +1,4 @@
$ = require 'jquery'
{$$} = require 'space-pen'
Range = require 'range'
SelectList = require 'select-list'
@@ -22,7 +23,8 @@ class AutocompleteView extends SelectList
itemForElement: (match) ->
$$ ->
@li match.word
@li =>
@span match.word
handleEvents: ->
@editor.on 'editor:path-changed', => @setCurrentBuffer(@editor.getBuffer())
@@ -156,7 +158,15 @@ class AutocompleteView extends SelectList
{prefix, suffix}
afterAttach: (onDom) ->
if onDom
widestCompletion = parseInt(@css('min-width')) or 0
@list.find('span').each ->
widestCompletion = Math.max(widestCompletion, $(this).outerWidth())
@list.width(widestCompletion)
@width(@list.outerWidth())
populateList: ->
super()
super
@setPosition()
@@ -416,3 +416,12 @@ describe "AutocompleteView", ->
editor.trigger 'core:move-up'
expect(editor.getCursorBufferPosition().row).toBe 0
it "sets the width of the view to be wide enough to contain the longest completion without scrolling", ->
editor.attachToDom()
editor.insertText('thisIsAReallyReallyReallyLongCompletion ')
editor.moveCursorToBottom()
editor.insertNewline
editor.insertText('t')
autocomplete.attach()
expect(autocomplete.list.prop('scrollWidth')).toBe autocomplete.list.width()
@@ -4,7 +4,20 @@
}
.autocomplete ol {
box-sizing: content-box;
position: relative;
overflow-y: scroll;
max-height: 200px;
}
.select-list.autocomplete ol li {
padding-top: 5px;
padding-bottom: 5px;
padding-left: 0px;
padding-right: 0px;
}
.autocomplete span {
padding-left: 5px;
padding-right: 5px;
}
@@ -49,7 +49,7 @@ class CommandPaletteView extends SelectList
keyBindings = @keyBindings
$$ ->
@li class: 'event', 'data-event-name': eventName, =>
@span eventDescription, class: 'label', title: eventName
@span eventDescription, title: eventName
@div class: 'right', =>
for binding in keyBindings[eventName] ? []
@kbd binding, class: 'key-binding'
@@ -24,8 +24,8 @@ describe "CommandPalette", ->
eventLi = palette.list.children("[data-event-name='#{eventName}']")
if description
expect(eventLi).toExist()
expect(eventLi.find('.label')).toHaveText(description)
expect(eventLi.find('.label').attr('title')).toBe(eventName)
expect(eventLi.find('span')).toHaveText(description)
expect(eventLi.find('span').attr('title')).toBe(eventName)
for binding in keyBindings[eventName] ? []
expect(eventLi.find(".key-binding:contains(#{binding})")).toExist()
else
@@ -40,8 +40,8 @@ describe "CommandPalette", ->
description = editorEvents[eventName] unless description
if description
expect(eventLi).toExist()
expect(eventLi.find('.label')).toHaveText(description)
expect(eventLi.find('.label').attr('title')).toBe(eventName)
expect(eventLi.find('span')).toHaveText(description)
expect(eventLi.find('span').attr('title')).toBe(eventName)
else
expect(eventLi).not.toExist()
@@ -92,8 +92,8 @@ describe "CommandPalette", ->
eventLi = palette.list.children("[data-event-name='#{eventName}']")
if description
expect(eventLi).toExist()
expect(eventLi.find('.label')).toHaveText(description)
expect(eventLi.find('.label').attr('title')).toBe(eventName)
expect(eventLi.find('span')).toHaveText(description)
expect(eventLi.find('span').attr('title')).toBe(eventName)
for binding in keyBindings[eventName] ? []
expect(eventLi.find(".key-binding:contains(#{binding})")).toExist()
else
@@ -13,9 +13,8 @@ class CommandPanelView extends View
@div class: 'command-panel tool-panel', =>
@div class: 'loading is-loading', outlet: 'loadingMessage', 'Searching...'
@div class: 'header', outlet: 'previewHeader', =>
@ul outlet: 'expandCollapse', class: 'expand-collapse', =>
@li class: 'expand', 'Expand All'
@li class: 'collapse', 'Collapse All'
@button outlet: 'collapseAll', class: 'btn btn-mini pull-right', 'Collapse All'
@button outlet: 'expandAll', class: 'btn btn-mini pull-right', 'Expand All'
@span outlet: 'previewCount', class: 'preview-count'
@subview 'previewList', new PreviewList(rootView)
@@ -47,8 +46,8 @@ class CommandPanelView extends View
@subscribeToCommand rootView, 'command-panel:repeat-relative-address-in-reverse', => @repeatRelativeAddress(reverse: true)
@subscribeToCommand rootView, 'command-panel:set-selection-as-regex-address', => @setSelectionAsLastRelativeAddress()
@on 'click', '.expand', @onExpandAll
@on 'click', '.collapse', @onCollapseAll
@expandAll.on 'click', @onExpandAll
@collapseAll.on 'click', @onCollapseAll
@previewList.hide()
@previewHeader.hide()
@@ -6,7 +6,7 @@ class CompositeCommand
constructor: (@subcommands) ->
execute: (project, editSession) ->
currentRanges = editSession?.getSelectedBufferRanges()
currentRanges = editSession?.getSelectedBufferRanges() ? []
@executeCommands(@subcommands, project, editSession, currentRanges)
executeCommands: (commands, project, editSession, ranges) ->
@@ -60,4 +60,3 @@ class CompositeCommand
isRelativeAddress: ->
_.all(@subcommands, (command) -> command.isAddress() and command.isRelative())
+3 -2
Ver Arquivo
@@ -14,7 +14,7 @@ class PathView extends View
@span outlet: 'description', class: 'path-match-number'
@ul outlet: 'matches', class: 'matches', =>
initialize: ({@previewList}) ->
initialize: ({@previewList, operationCount}) ->
@pathDetails.on 'mousedown', => @toggle(true)
@subscribe @previewList, 'command-panel:collapse-result', =>
if @isSelected()
@@ -27,9 +27,10 @@ class PathView extends View
@toggle(true)
false
@description.text("(#{operationCount})")
addOperation: (operation) ->
@matches.append new OperationView({operation, @previewList})
@description.text("(#{@matches.find('li').length})")
isSelected: ->
@hasClass('selected') or @find('.selected').length
@@ -60,7 +60,7 @@ class PreviewList extends ScrollView
pathViewForPath: (path) ->
pathView = @viewsForPath[path]
if not pathView
pathView = new PathView({path: path, previewList: this})
pathView = new PathView({path: path, previewList: this, operationCount: @getPathOperationCount(path)})
@viewsForPath[path] = pathView
@append(pathView)
pathView
@@ -97,6 +97,9 @@ class PreviewList extends ScrollView
getPathCount: ->
_.keys(_.groupBy(@operations, (operation) -> operation.getPath())).length
getPathOperationCount: (path) ->
@operations.filter((operation) -> path is operation.getPath()).length
getOperations: ->
new Array(@operations...)
@@ -205,6 +205,17 @@ describe "CommandInterpreter", ->
runs ->
expect(interpreter.lastRelativeAddress.subcommands[0].regex.toString()).toEqual "/Array/"
describe "when there is no active edit session", ->
it "returns no error messages and does not throw an error", ->
errorMessages = null
waitsForPromise ->
interpreter.eval('/something').done (results) ->
{errorMessages} = results
runs ->
expect(errorMessages.length).toBe 0
describe "address range", ->
describe "when two addresses are specified", ->
it "selects from the begining of the left address to the end of the right address", ->
@@ -139,7 +139,8 @@ describe "CommandPanel", ->
expect(commandPanel.previewList).toBeVisible()
it "shows the expand and collapse all buttons", ->
expect(commandPanel.find('.expand-collapse')).toBeVisible()
expect(commandPanel.collapseAll).toBeVisible()
expect(commandPanel.expandAll).toBeVisible()
describe "when the preview list is focused", ->
it "hides the command panel", ->
@@ -92,6 +92,11 @@
padding: 1px;
}
}
.matches {
list-style-type: none;
margin: 0;
}
}
.header:after {
@@ -105,6 +110,7 @@
.expand-collapse {
float: right;
-webkit-user-select: none;
margin: 0;
li {
display: inline-block;
@@ -113,6 +119,7 @@
margin-left: 5px;
padding: 5px 10px;
border-radius: 3px;
line-height: normal;
}
}
@@ -124,9 +131,15 @@
-webkit-flex: 1;
}
}
}
.error-messages {
padding: 5px 1em;
color: white;
.error-messages {
list-style-type: none;
margin: 0;
padding: 5px 1em;
color: white;
}
.btn {
margin-left: 5px;
}
}
@@ -59,7 +59,7 @@ class FuzzyFinderView extends SelectList
else
typeClass = 'text-name'
@span fsUtils.base(path), class: "file label #{typeClass}"
@span fsUtils.base(path), class: "file #{typeClass}"
if folder = project.relativize(fsUtils.directory(path))
@span " - #{folder}/", class: 'directory'
@@ -41,7 +41,7 @@ module.exports =
createView: ->
unless @fuzzyFinderView
@loadPathsTask?.abort()
FuzzyFinderView = require 'fuzzy-finder/lib/fuzzy-finder-view'
FuzzyFinderView = require './fuzzy-finder-view'
@fuzzyFinderView = new FuzzyFinderView()
if @projectPaths?.length > 0 and not @fuzzyFinderView.projectPaths?
@fuzzyFinderView.projectPaths = @projectPaths

Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais