mirror of
https://github.com/atom/atom.git
synced 2026-01-23 13:58:08 -05:00
Merge remote-tracking branch 'origin/dev'
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,7 +2,6 @@
|
||||
.project
|
||||
.svn
|
||||
.nvm-version
|
||||
atom-build
|
||||
atom.xcodeproj
|
||||
build
|
||||
.xcodebuild-info
|
||||
@@ -10,3 +9,4 @@ node_modules
|
||||
npm-debug.log
|
||||
/tags
|
||||
/cef/
|
||||
/sources.gypi
|
||||
|
||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -51,7 +51,7 @@
|
||||
url = https://github.com/mmcgrana/textmate-clojure
|
||||
[submodule "prebuilt-cef"]
|
||||
path = prebuilt-cef
|
||||
url = git@github.com:github/prebuilt-cef.git
|
||||
url = https://github.com/github/prebuilt-cef
|
||||
[submodule "vendor/packages/yaml.tmbundle"]
|
||||
path = vendor/packages/yaml.tmbundle
|
||||
url = https://github.com/textmate/yaml.tmbundle.git
|
||||
|
||||
0
CHANGELOG.md
Normal file
0
CHANGELOG.md
Normal file
16
README.md
16
README.md
@@ -1,19 +1,19 @@
|
||||
# Atom — Futuristic Text Editing
|
||||
|
||||

|
||||

|
||||
|
||||
Check out our [documentation on the docs tab](https://github.com/github/atom/docs).
|
||||
|
||||
## Building from source
|
||||
|
||||
Requirements
|
||||
### Requirements
|
||||
|
||||
**Mountain Lion**
|
||||
* Mountain Lion
|
||||
* The Setup™ or Boxen
|
||||
* Xcode (available in the App Store)
|
||||
|
||||
**The Setup™**
|
||||
### Installation
|
||||
|
||||
**Xcode** (Get Xcode from the App Store (ugh, I know))
|
||||
1. `gh-setup atom`
|
||||
|
||||
1. gh-setup atom
|
||||
|
||||
2. cd ~/github/atom && `rake install`
|
||||
2. `cd ~/github/atom && rake install`
|
||||
|
||||
29
Rakefile
29
Rakefile
@@ -1,9 +1,9 @@
|
||||
ATOM_SRC_PATH = File.dirname(__FILE__)
|
||||
BUILD_DIR = 'atom-build'
|
||||
BUILD_DIR = '/tmp/atom-build'
|
||||
|
||||
desc "Build Atom via `xcodebuild`"
|
||||
task :build => "create-xcode-project" do
|
||||
command = "xcodebuild -target Atom -configuration Release SYMROOT=#{BUILD_DIR}"
|
||||
command = "xcodebuild -target Atom SYMROOT=#{BUILD_DIR}"
|
||||
output = `#{command}`
|
||||
if $?.exitstatus != 0
|
||||
$stderr.puts "Error #{$?.exitstatus}:\n#{output}"
|
||||
@@ -14,6 +14,7 @@ end
|
||||
desc "Create xcode project from gyp file"
|
||||
task "create-xcode-project" => "update-cef" do
|
||||
`rm -rf atom.xcodeproj`
|
||||
`script/generate-sources-gypi`
|
||||
`gyp --depth=. -D CODE_SIGN="#{ENV['CODE_SIGN']}" atom.gyp`
|
||||
end
|
||||
|
||||
@@ -42,13 +43,17 @@ task :install => [:clean, :build] do
|
||||
# Install Atom.app
|
||||
dest_path = "/Applications/#{File.basename(path)}"
|
||||
`rm -rf #{dest_path}`
|
||||
`cp -r #{path} #{File.expand_path(dest_path)}`
|
||||
`cp -a #{path} #{File.expand_path(dest_path)}`
|
||||
|
||||
# Install atom cli
|
||||
if File.directory?("/opt/boxen")
|
||||
cli_path = "/opt/boxen/bin/atom"
|
||||
else
|
||||
elsif File.directory?("/opt/github")
|
||||
cli_path = "/opt/github/bin/atom"
|
||||
elsif File.directory?("/usr/local")
|
||||
cli_path = "/usr/local/bin/atom"
|
||||
else
|
||||
raise "Missing directory for `atom` binary"
|
||||
end
|
||||
|
||||
FileUtils.cp("#{ATOM_SRC_PATH}/atom.sh", cli_path)
|
||||
@@ -59,18 +64,6 @@ task :install => [:clean, :build] do
|
||||
puts "\033[32mAtom is installed at `#{dest_path}`. Atom cli is installed at `#{cli_path}`\033[0m"
|
||||
end
|
||||
|
||||
desc "Package up the app for speakeasy"
|
||||
task :package => ["setup-codesigning", "build"] do
|
||||
path = application_path()
|
||||
exit 1 if not path
|
||||
|
||||
dest_path = '/tmp/atom-for-speakeasy/Atom.tar.bz2'
|
||||
`mkdir -p $(dirname #{dest_path})`
|
||||
`rm -rf #{dest_path}`
|
||||
`tar --directory $(dirname #{path}) -jcf #{dest_path} $(basename #{path})`
|
||||
`open $(dirname #{dest_path})`
|
||||
end
|
||||
|
||||
task "setup-codesigning" do
|
||||
ENV['CODE_SIGN'] = "Developer ID Application: GitHub"
|
||||
end
|
||||
@@ -90,7 +83,7 @@ task :clean do
|
||||
end
|
||||
|
||||
desc "Run the specs"
|
||||
task :test => ["update-cef", "clone-default-bundles", "build"] do
|
||||
task :test => ["clean", "update-cef", "clone-default-bundles", "build"] do
|
||||
`pkill Atom`
|
||||
if path = application_path()
|
||||
cmd = "#{path}/Contents/MacOS/Atom --test --resource-path=#{ATOM_SRC_PATH} 2> /dev/null"
|
||||
@@ -107,7 +100,7 @@ task :benchmark do
|
||||
end
|
||||
|
||||
task :nof do
|
||||
system %{find . -name *spec.coffee | grep --invert-match --regexp "#{BUILD_DIR}\\|##package-name##" | xargs sed -E -i "" "s/f+(it|describe) +(['\\"])/\\1 \\2/g"}
|
||||
system %{find . -name *spec.coffee | grep --invert-match --regexp "#{BUILD_DIR}\\|__package-name__" | xargs sed -E -i "" "s/f+(it|describe) +(['\\"])/\\1 \\2/g"}
|
||||
end
|
||||
|
||||
task :tags do
|
||||
|
||||
288
atom.gyp
288
atom.gyp
@@ -16,13 +16,30 @@
|
||||
'toolkit_uses_gtk%': 0,
|
||||
}],
|
||||
],
|
||||
'fix_framework_link_command': [
|
||||
'install_name_tool',
|
||||
'-change',
|
||||
'@executable_path/libcef.dylib',
|
||||
'@rpath/Chromium Embedded Framework.framework/Libraries/libcef.dylib',
|
||||
'-change',
|
||||
'@executable_path/../Frameworks/CocoaOniguruma.framework/Versions/A/CocoaOniguruma',
|
||||
'@rpath/CocoaOniguruma.framework/Versions/A/CocoaOniguruma',
|
||||
'-change',
|
||||
'@loader_path/../Frameworks/Sparkle.framework/Versions/A/Sparkle',
|
||||
'@rpath/Sparkle.framework/Versions/A/Sparkle',
|
||||
'-change',
|
||||
'@executable_path/libgit2.0.17.0.dylib',
|
||||
'@rpath/libgit2.framework/Libraries/libgit2.0.17.0.dylib',
|
||||
'${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}'
|
||||
],
|
||||
},
|
||||
'includes': [
|
||||
'cef/cef_paths2.gypi',
|
||||
'git2/libgit2.gypi',
|
||||
'sources.gypi',
|
||||
],
|
||||
'target_defaults': {
|
||||
'default_configuration': 'Debug',
|
||||
'default_configuration': 'Release',
|
||||
'configurations': {
|
||||
'Debug': {
|
||||
'defines': ['DEBUG=1'],
|
||||
@@ -45,39 +62,20 @@
|
||||
'mac_bundle': 1,
|
||||
'msvs_guid': 'D22C6F51-AA2D-457C-B579-6C97A96C724D',
|
||||
'dependencies': [
|
||||
'libcef_dll_wrapper',
|
||||
'atom_framework',
|
||||
],
|
||||
'defines': [
|
||||
'USING_CEF_SHARED',
|
||||
],
|
||||
'include_dirs': [ '.', 'cef', 'git2' ],
|
||||
'mac_framework_dirs': [ 'native/frameworks' ],
|
||||
'libraries': [ 'native/frameworks/CocoaOniguruma.framework', 'native/frameworks/Sparkle.framework'],
|
||||
'sources': [
|
||||
'<@(includes_common)',
|
||||
'<@(includes_wrapper)',
|
||||
'native/main_mac.mm',
|
||||
'native/atom_application.h',
|
||||
'native/atom_application.mm',
|
||||
'native/atom_cef_app.h',
|
||||
'native/atom_window_controller.h',
|
||||
'native/atom_window_controller.mm',
|
||||
'native/atom_cef_client_mac.mm',
|
||||
'native/atom_cef_client.cpp',
|
||||
'native/atom_cef_client.h',
|
||||
'native/message_translation.cpp',
|
||||
'native/message_translation.h',
|
||||
'native/main.cpp',
|
||||
],
|
||||
'mac_bundle_resources': [
|
||||
'native/mac/atom.icns',
|
||||
'native/mac/file.icns',
|
||||
'native/mac/speakeasy.pem',
|
||||
'native/mac/English.lproj/MainMenu.xib',
|
||||
'native/mac/English.lproj/AtomWindow.xib',
|
||||
],
|
||||
'xcode_settings': {
|
||||
'INFOPLIST_FILE': 'native/mac/info.plist',
|
||||
'OTHER_LDFLAGS': ['-Wl,-headerpad_max_install_names'], # Necessary to avoid an "install_name_tool: changing install names or rpaths can't be redone" error.
|
||||
'INFOPLIST_FILE': 'native/mac/Atom-Info.plist',
|
||||
'LD_RUNPATH_SEARCH_PATHS': '@executable_path/../Frameworks',
|
||||
},
|
||||
'conditions': [
|
||||
['CODE_SIGN' , {
|
||||
@@ -142,6 +140,8 @@
|
||||
{
|
||||
'destination': '<(PRODUCT_DIR)/Atom.app/Contents/Frameworks',
|
||||
'files': [
|
||||
'<(PRODUCT_DIR)/Atom Helper.app',
|
||||
'<(PRODUCT_DIR)/Atom.framework',
|
||||
'native/frameworks/CocoaOniguruma.framework',
|
||||
'native/frameworks/Sparkle.framework',
|
||||
],
|
||||
@@ -152,40 +152,18 @@
|
||||
'git2/frameworks/libgit2.0.17.0.dylib',
|
||||
],
|
||||
},
|
||||
{
|
||||
'destination': '<(PRODUCT_DIR)/Atom.app/Contents/Frameworks/Chromium Embedded Framework.framework',
|
||||
'files': [
|
||||
'cef/Resources',
|
||||
],
|
||||
},
|
||||
],
|
||||
'postbuilds': [
|
||||
{
|
||||
'postbuild_name': 'Copy and Compile Static Files',
|
||||
'action': [
|
||||
'script/copy-files-to-bundle'
|
||||
],
|
||||
},
|
||||
{
|
||||
'postbuild_name': 'Copy Helper App',
|
||||
'action': [
|
||||
'cp',
|
||||
'-r',
|
||||
'${BUILT_PRODUCTS_DIR}/Atom Helper.app',
|
||||
'${BUILT_PRODUCTS_DIR}/Atom.app/Contents/Frameworks',
|
||||
],
|
||||
},
|
||||
{
|
||||
'postbuild_name': 'Fix Framework Link',
|
||||
'action': [
|
||||
'install_name_tool',
|
||||
'-change',
|
||||
'@executable_path/libcef.dylib',
|
||||
'@executable_path/../Frameworks/Chromium Embedded Framework.framework/Libraries/libcef.dylib',
|
||||
'${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}'
|
||||
],
|
||||
},
|
||||
{
|
||||
'postbuild_name': 'Copy Framework Resources Directory',
|
||||
'action': [
|
||||
'cp',
|
||||
'-r',
|
||||
'cef/Resources',
|
||||
'${BUILT_PRODUCTS_DIR}/Atom.app/Contents/Frameworks/Chromium Embedded Framework.framework/'
|
||||
'<@(fix_framework_link_command)',
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -204,6 +182,12 @@
|
||||
'Atom',
|
||||
],
|
||||
},
|
||||
{
|
||||
'postbuild_name': 'Print env for Constructicon',
|
||||
'action': [
|
||||
'env',
|
||||
],
|
||||
},
|
||||
],
|
||||
'link_settings': {
|
||||
'libraries': [
|
||||
@@ -231,6 +215,89 @@
|
||||
}],
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'atom_framework',
|
||||
'product_name': 'Atom',
|
||||
'type': 'shared_library',
|
||||
'mac_bundle': 1,
|
||||
'dependencies': [
|
||||
'generated_sources',
|
||||
'libcef_dll_wrapper',
|
||||
],
|
||||
'defines': [
|
||||
'USING_CEF_SHARED',
|
||||
],
|
||||
'xcode_settings': {
|
||||
'INFOPLIST_FILE': 'native/mac/framework-info.plist',
|
||||
'LD_DYLIB_INSTALL_NAME': '@rpath/Atom.framework/Atom',
|
||||
},
|
||||
'include_dirs': [ '.', 'cef', 'git2' ],
|
||||
'mac_framework_dirs': [ 'native/frameworks' ],
|
||||
'sources': [
|
||||
'<@(includes_common)',
|
||||
'<@(includes_wrapper)',
|
||||
'native/atom_application.h',
|
||||
'native/atom_application.mm',
|
||||
'native/atom_cef_app.h',
|
||||
'native/atom_cef_app.h',
|
||||
'native/atom_cef_client.cpp',
|
||||
'native/atom_cef_client.h',
|
||||
'native/atom_cef_client_mac.mm',
|
||||
'native/atom_cef_render_process_handler.h',
|
||||
'native/atom_cef_render_process_handler.mm',
|
||||
'native/atom_window_controller.h',
|
||||
'native/atom_window_controller.mm',
|
||||
'native/atom_main.h',
|
||||
'native/atom_main_mac.mm',
|
||||
'native/message_translation.cpp',
|
||||
'native/message_translation.cpp',
|
||||
'native/message_translation.h',
|
||||
'native/message_translation.h',
|
||||
'native/path_watcher.h',
|
||||
'native/path_watcher.mm',
|
||||
'native/v8_extensions/atom.h',
|
||||
'native/v8_extensions/atom.mm',
|
||||
'native/v8_extensions/git.h',
|
||||
'native/v8_extensions/git.mm',
|
||||
'native/v8_extensions/native.h',
|
||||
'native/v8_extensions/native.mm',
|
||||
'native/v8_extensions/onig_reg_exp.h',
|
||||
'native/v8_extensions/onig_reg_exp.mm',
|
||||
'native/v8_extensions/onig_scanner.h',
|
||||
'native/v8_extensions/onig_scanner.mm',
|
||||
'native/v8_extensions/readtags.c',
|
||||
'native/v8_extensions/readtags.h',
|
||||
'native/v8_extensions/tags.h',
|
||||
'native/v8_extensions/tags.mm',
|
||||
],
|
||||
'link_settings': {
|
||||
'libraries': [
|
||||
'$(SDKROOT)/System/Library/Frameworks/AppKit.framework',
|
||||
'git2/frameworks/libgit2.0.17.0.dylib',
|
||||
'native/frameworks/CocoaOniguruma.framework',
|
||||
'native/frameworks/Sparkle.framework',
|
||||
],
|
||||
},
|
||||
'mac_bundle_resources': [
|
||||
'native/mac/English.lproj/AtomWindow.xib',
|
||||
'native/mac/English.lproj/MainMenu.xib',
|
||||
],
|
||||
'postbuilds': [
|
||||
{
|
||||
'postbuild_name': 'Copy Static Files',
|
||||
'action': [
|
||||
'script/copy-files-to-bundle',
|
||||
'<(compiled_sources_dir_xcode)',
|
||||
],
|
||||
},
|
||||
{
|
||||
'postbuild_name': 'Fix Framework Link',
|
||||
'action': [
|
||||
'<@(fix_framework_link_command)',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'libcef_dll_wrapper',
|
||||
'type': 'static_library',
|
||||
@@ -253,6 +320,48 @@
|
||||
],
|
||||
}
|
||||
},
|
||||
{
|
||||
'target_name': 'generated_sources',
|
||||
'type': 'none',
|
||||
'sources': [
|
||||
'<@(coffee_sources)',
|
||||
'<@(cson_sources)',
|
||||
],
|
||||
'rules': [
|
||||
{
|
||||
'rule_name': 'coffee',
|
||||
'extension': 'coffee',
|
||||
'inputs': [
|
||||
'script/compile-coffee',
|
||||
],
|
||||
'outputs': [
|
||||
'<(compiled_sources_dir)/<(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT).js',
|
||||
],
|
||||
'action': [
|
||||
'sh',
|
||||
'script/compile-coffee',
|
||||
'<(RULE_INPUT_PATH)',
|
||||
'<(compiled_sources_dir)/<(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT).js',
|
||||
],
|
||||
},
|
||||
{
|
||||
'rule_name': 'cson2json',
|
||||
'extension': 'cson',
|
||||
'inputs': [
|
||||
'script/compile-cson',
|
||||
],
|
||||
'outputs': [
|
||||
'<(compiled_sources_dir)/<(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT).json',
|
||||
],
|
||||
'action': [
|
||||
'sh',
|
||||
'script/compile-cson',
|
||||
'<(RULE_INPUT_PATH)',
|
||||
'<(compiled_sources_dir)/<(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT).json',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
'conditions': [
|
||||
['os_posix==1 and OS!="mac" and OS!="android" and gcc_version==46', {
|
||||
@@ -271,46 +380,15 @@
|
||||
'product_name': 'Atom Helper',
|
||||
'mac_bundle': 1,
|
||||
'dependencies': [
|
||||
'libcef_dll_wrapper',
|
||||
'atom_framework',
|
||||
],
|
||||
'defines': [
|
||||
'USING_CEF_SHARED',
|
||||
'PROCESS_HELPER_APP',
|
||||
],
|
||||
'include_dirs': [ '.', 'cef', 'git2' ],
|
||||
'mac_framework_dirs': [ 'native/frameworks' ],
|
||||
'link_settings': {
|
||||
'libraries': [
|
||||
'$(SDKROOT)/System/Library/Frameworks/AppKit.framework',
|
||||
],
|
||||
},
|
||||
'libraries': [
|
||||
'native/frameworks/CocoaOniguruma.framework',
|
||||
'git2/frameworks/libgit2.0.17.0.dylib',
|
||||
],
|
||||
'sources': [
|
||||
'native/atom_cef_app.h',
|
||||
'native/atom_cef_render_process_handler.h',
|
||||
'native/atom_cef_render_process_handler.mm',
|
||||
'native/message_translation.cpp',
|
||||
'native/message_translation.h',
|
||||
'native/path_watcher.mm',
|
||||
'native/path_watcher.h',
|
||||
'native/main_helper_mac.mm',
|
||||
'native/v8_extensions/native.mm',
|
||||
'native/v8_extensions/native.h',
|
||||
'native/v8_extensions/onig_reg_exp.mm',
|
||||
'native/v8_extensions/onig_reg_exp.h',
|
||||
'native/v8_extensions/onig_scanner.mm',
|
||||
'native/v8_extensions/onig_scanner.h',
|
||||
'native/v8_extensions/atom.mm',
|
||||
'native/v8_extensions/atom.h',
|
||||
'native/v8_extensions/git.mm',
|
||||
'native/v8_extensions/git.h',
|
||||
'native/v8_extensions/readtags.h',
|
||||
'native/v8_extensions/readtags.c',
|
||||
'native/v8_extensions/tags.h',
|
||||
'native/v8_extensions/tags.mm',
|
||||
'native/main.cpp',
|
||||
],
|
||||
# TODO(mark): For now, don't put any resources into this app. Its
|
||||
# resources directory will be a symbolic link to the browser app's
|
||||
@@ -320,45 +398,13 @@
|
||||
],
|
||||
'xcode_settings': {
|
||||
'INFOPLIST_FILE': 'native/mac/helper-info.plist',
|
||||
'OTHER_LDFLAGS': ['-Wl,-headerpad_max_install_names'], # Necessary to avoid an "install_name_tool: changing install names or rpaths can't be redone" error.
|
||||
'LD_RUNPATH_SEARCH_PATHS': '@executable_path/../../..',
|
||||
},
|
||||
'copies': [
|
||||
{
|
||||
'destination': '<(PRODUCT_DIR)/Atom Helper.app/Contents/Frameworks',
|
||||
'files': [
|
||||
'native/frameworks/CocoaOniguruma.framework',
|
||||
],
|
||||
},
|
||||
],
|
||||
'postbuilds': [
|
||||
{
|
||||
# The framework defines its load-time path
|
||||
# (DYLIB_INSTALL_NAME_BASE) relative to the main executable
|
||||
# (chrome). A different relative path needs to be used in
|
||||
# atom_helper_app.
|
||||
'postbuild_name': 'Fix CEF Framework Link',
|
||||
'postbuild_name': 'Fix Framework Link',
|
||||
'action': [
|
||||
'install_name_tool',
|
||||
'-change',
|
||||
'@executable_path/libcef.dylib',
|
||||
'@executable_path/../../../../Frameworks/Chromium Embedded Framework.framework/Libraries/libcef.dylib',
|
||||
'${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}'
|
||||
],
|
||||
},
|
||||
{
|
||||
'postbuild_name': 'Fix libgit2 Framework Link',
|
||||
'action': [
|
||||
'install_name_tool',
|
||||
'-change',
|
||||
'@executable_path/libgit2.0.17.0.dylib',
|
||||
'@executable_path/../../../../Frameworks/libgit2.framework/Libraries/libgit2.0.17.0.dylib',
|
||||
'${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}'
|
||||
],
|
||||
},
|
||||
{
|
||||
'postbuild_name': 'Copy and Compile Static Files',
|
||||
'action': [
|
||||
'script/copy-files-to-bundle'
|
||||
'<@(fix_framework_link_command)',
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -7,7 +7,7 @@ Config = require 'config'
|
||||
Project = require 'project'
|
||||
|
||||
require 'window'
|
||||
requireStylesheet "jasmine.css"
|
||||
requireStylesheet "jasmine.less"
|
||||
|
||||
# Load TextMate bundles, which specs rely on (but not other packages)
|
||||
atom.loadTextMatePackages()
|
||||
@@ -127,4 +127,3 @@ $.fn.textInput = (data) ->
|
||||
event = document.createEvent 'TextEvent'
|
||||
event.initTextEvent('textInput', true, true, window, data)
|
||||
this.each -> this.dispatchEvent(event)
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ describe "TokenizedBuffer.", ->
|
||||
[languageMode, buffer] = []
|
||||
|
||||
beforeEach ->
|
||||
editSession = benchmarkFixturesProject.buildEditSessionForPath('medium.coffee')
|
||||
editSession = benchmarkFixturesProject.buildEditSession('medium.coffee')
|
||||
{ languageMode, buffer } = editSession
|
||||
|
||||
benchmark "construction", 20, ->
|
||||
|
||||
@@ -11,7 +11,7 @@ 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 [keymaps](#keymaps) section to learn how.
|
||||
command, refer to the [key bindings](#customizing-key-bindings) section to learn how.
|
||||
|
||||

|
||||
|
||||
@@ -157,10 +157,10 @@ its own namespace.
|
||||
- hideGitIgnoredFiles: Whether files in the .gitignore should be hidden
|
||||
- ignoredNames: File names to ignore across all of atom (not fully implemented)
|
||||
- themes: An array of theme names to load, in cascading order
|
||||
- autosave: Save a resource when its view loses focus
|
||||
- editor
|
||||
- autoIndent: Enable/disable basic auto-indent (defaults to true)
|
||||
- autoIndentOnPaste: Enable/disable auto-indented pasted text (defaults to false)
|
||||
- autosave: Save a file when an editor loses focus
|
||||
- nonWordCharacters: A string of non-word characters to define word boundaries
|
||||
- fontSize
|
||||
- fontFamily
|
||||
|
||||
@@ -7,7 +7,7 @@ read config settings. You can read a value from `config` with `config.get`:
|
||||
|
||||
```coffeescript
|
||||
# read a value with `config.get`
|
||||
@autosave() if config.get "editor.autosave"
|
||||
@autosave() if config.get "core.autosave"
|
||||
```
|
||||
|
||||
Or you can use `observeConfig` to track changes from a view object.
|
||||
@@ -47,7 +47,7 @@ the following way:
|
||||
|
||||
```coffeescript
|
||||
# basic key update
|
||||
config.set("editor.autosave", true)
|
||||
config.set("core.autosave", true)
|
||||
|
||||
# if you mutate a config key, you'll need to call `config.update` to inform
|
||||
# observers of the change
|
||||
|
||||
97
docs/internals/serialization.md
Normal file
97
docs/internals/serialization.md
Normal file
@@ -0,0 +1,97 @@
|
||||
## Serialization in Atom
|
||||
|
||||
When a window is refreshed or restored from a previous session, the view and its
|
||||
associated objects are *deserialized* from a JSON representation that was stored
|
||||
during the window's previous shutdown. For your own views and objects to be
|
||||
compatible with refreshing, you'll need to make them play nicely with the
|
||||
serializing and deserializing.
|
||||
|
||||
### Package Serialization Hook
|
||||
|
||||
Your package's main module can optionally include a `serialize` method, which
|
||||
will be called before your package is deactivated. You should return JSON, which
|
||||
will be handed back to you as an argument to `activate` next time it is called.
|
||||
In the following example, the package keeps an instance of `MyObject` in the
|
||||
same state across refreshes.
|
||||
|
||||
```coffee-script
|
||||
module.exports =
|
||||
activate: (state) ->
|
||||
@myObject =
|
||||
if state
|
||||
deserialize(state)
|
||||
else
|
||||
new MyObject("Hello")
|
||||
|
||||
serialize: ->
|
||||
@myObject.serialize()
|
||||
```
|
||||
|
||||
### Serialization Methods
|
||||
|
||||
```coffee-script
|
||||
class MyObject
|
||||
registerDeserializer(this)
|
||||
@deserialize: ({data}) -> new MyObject(data)
|
||||
constructor: (@data) ->
|
||||
serialize: -> { deserializer: 'MyObject', data: @data }
|
||||
```
|
||||
|
||||
#### .serialize()
|
||||
Objects that you want to serialize should implement `.serialize()`. This method
|
||||
should return a serializable object, and it must contain a key named
|
||||
`deserializer` whose value is the name of a registered deserializer that can
|
||||
convert the rest of the data to an object. It's usually just the name of the
|
||||
class itself.
|
||||
|
||||
#### @deserialize(data)
|
||||
The other side of the coin is the `deserialize` method, which is usually a
|
||||
class-level method on the same class that implements `serialize`. This method's
|
||||
job is to convert a state object returned from a previous call `serialize` back
|
||||
into a genuine object.
|
||||
|
||||
#### registerDeserializer(klass)
|
||||
You need to call the global `registerDeserializer` method with your class in
|
||||
order to make it available to the deserialization system. Now you can call the
|
||||
global `deserialize` method with state returned from `serialize`, and your
|
||||
class's `deserialize` method will be selected automatically.
|
||||
|
||||
### Versioning
|
||||
|
||||
```coffee-script
|
||||
class MyObject
|
||||
@version: 2
|
||||
@deserialize: (state) -> ...
|
||||
serialize: -> { version: MyObject.version, ... }
|
||||
```
|
||||
|
||||
Your serializable class can optionally have a class-level `@version` property
|
||||
and include a `version` key in its serialized state. When deserializing, Atom
|
||||
will only attempt to call deserialize if the two versions match, and otherwise
|
||||
return undefined. We plan on implementing a migration system in the future, but
|
||||
this at least protects you from improperly deserializing old state. If you find
|
||||
yourself in dire need of the migration system, let us know.
|
||||
|
||||
### Deferred Package Deserializers
|
||||
|
||||
If your package defers loading on startup with an `activationEvents` property in
|
||||
its `package.cson`, your deserializers won't be loaded until your package is
|
||||
activated. If you want to deserialize an object from your package on startup,
|
||||
this could be a problem.
|
||||
|
||||
The solution is to also supply a `deferredDeserializers` array in your
|
||||
`package.cson` with the names of all your deserializers. When Atom attempts to
|
||||
deserialize some state whose `deserializer` matches one of these names, it will
|
||||
load your package first so it can register any necessary deserializers before
|
||||
proceeding.
|
||||
|
||||
For example, the markdown preview package doesn't fully load until a preview is
|
||||
triggered. But if you refresh a window with a preview pane, it loads the
|
||||
markdown package early so Atom can deserialize the view correctly.
|
||||
|
||||
```coffee-script
|
||||
# markdown-preview/package.cson
|
||||
'activationEvents': 'markdown-preview:toggle': '.editor'
|
||||
'deferredDeserializers': ['MarkdownPreviewView']
|
||||
...
|
||||
```
|
||||
Binary file not shown.
@@ -18,6 +18,7 @@ class AtomCefClient;
|
||||
+ (CefSettings)createCefSettings;
|
||||
+ (NSDictionary *)parseArguments:(char **)argv count:(int)argc;
|
||||
- (void)open:(NSString *)path;
|
||||
- (void)openDev:(NSString *)path;
|
||||
- (void)open:(NSString *)path pidToKillWhenWindowCloses:(NSNumber *)pid;
|
||||
- (IBAction)runSpecs:(id)sender;
|
||||
- (IBAction)runBenchmarks:(id)sender;
|
||||
|
||||
@@ -4,17 +4,13 @@
|
||||
|
||||
#include "include/cef_app.h"
|
||||
|
||||
#ifdef PROCESS_HELPER_APP
|
||||
#include "atom_cef_render_process_handler.h"
|
||||
#endif
|
||||
|
||||
class AtomCefApp : public CefApp {
|
||||
|
||||
#ifdef PROCESS_HELPER_APP
|
||||
virtual CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() OVERRIDE {
|
||||
return CefRefPtr<CefRenderProcessHandler>(new AtomCefRenderProcessHandler);
|
||||
}
|
||||
#endif
|
||||
|
||||
IMPLEMENT_REFCOUNTING(AtomCefApp);
|
||||
};
|
||||
|
||||
@@ -34,7 +34,6 @@ void AtomCefRenderProcessHandler::OnWorkerContextCreated(int worker_id,
|
||||
void AtomCefRenderProcessHandler::OnWorkerContextReleased(int worker_id,
|
||||
const CefString& url,
|
||||
CefRefPtr<CefV8Context> context) {
|
||||
NSLog(@"Web worker context released");
|
||||
}
|
||||
|
||||
void AtomCefRenderProcessHandler::OnWorkerUncaughtException(int worker_id,
|
||||
|
||||
1
native/atom_main.h
Normal file
1
native/atom_main.h
Normal file
@@ -0,0 +1 @@
|
||||
__attribute__((visibility("default"))) int AtomMain(int argc, char* argv[]);
|
||||
@@ -1,3 +1,5 @@
|
||||
#import "atom_main.h"
|
||||
#import "atom_cef_app.h"
|
||||
#import "include/cef_application_mac.h"
|
||||
#import "native/atom_application.h"
|
||||
#include <sys/types.h>
|
||||
@@ -10,7 +12,15 @@ void listenForPathToOpen(int fd, NSString *socketPath);
|
||||
void activateOpenApp();
|
||||
BOOL isAppAlreadyOpen();
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
int AtomMain(int argc, char* argv[]) {
|
||||
// Check if we're being run as a secondary process.
|
||||
CefMainArgs main_args(argc, argv);
|
||||
CefRefPtr<CefApp> app(new AtomCefApp);
|
||||
int exitCode = CefExecuteProcess(main_args, app);
|
||||
if (exitCode >= 0)
|
||||
return exitCode;
|
||||
|
||||
// We're the main process.
|
||||
@autoreleasepool {
|
||||
handleBeingOpenedAgain(argc, argv);
|
||||
|
||||
@@ -18,8 +28,8 @@ int main(int argc, char* argv[]) {
|
||||
AtomApplication *application = [AtomApplication applicationWithArguments:argv count:argc];
|
||||
|
||||
NSString *mainNibName = [infoDictionary objectForKey:@"NSMainNibFile"];
|
||||
NSNib *mainNib = [[NSNib alloc] initWithNibNamed:mainNibName bundle:[NSBundle mainBundle]];
|
||||
[mainNib instantiateNibWithOwner:application topLevelObjects:nil];
|
||||
NSNib *mainNib = [[NSNib alloc] initWithNibNamed:mainNibName bundle:[NSBundle bundleWithIdentifier:@"com.github.atom.framework"]];
|
||||
[mainNib instantiateWithOwner:application topLevelObjects:nil];
|
||||
|
||||
CefRunMessageLoop();
|
||||
}
|
||||
@@ -34,8 +34,7 @@
|
||||
|
||||
_resourcePath = [atomApplication.arguments objectForKey:@"resource-path"];
|
||||
if (!alwaysUseBundleResourcePath && !_resourcePath) {
|
||||
NSString *defaultRepositoryPath = @"~/github/atom";
|
||||
defaultRepositoryPath = [defaultRepositoryPath stringByStandardizingPath];
|
||||
NSString *defaultRepositoryPath = [@"~/github/atom" stringByStandardizingPath];
|
||||
if ([defaultRepositoryPath characterAtIndex:0] == '/') {
|
||||
BOOL isDir = false;
|
||||
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:defaultRepositoryPath isDirectory:&isDir];
|
||||
@@ -45,8 +44,14 @@
|
||||
}
|
||||
|
||||
if (alwaysUseBundleResourcePath || !_resourcePath) {
|
||||
_resourcePath = [[NSBundle mainBundle] resourcePath];
|
||||
_resourcePath = [[NSBundle bundleForClass:self.class] resourcePath];
|
||||
}
|
||||
|
||||
if ([self isDevMode]) {
|
||||
[self displayDevIcon];
|
||||
}
|
||||
|
||||
_resourcePath = [_resourcePath stringByStandardizingPath];
|
||||
[_resourcePath retain];
|
||||
|
||||
if (!background) {
|
||||
@@ -69,7 +74,6 @@
|
||||
|
||||
- (id)initDevWithPath:(NSString *)path {
|
||||
_pathToOpen = [path retain];
|
||||
AtomApplication *atomApplication = (AtomApplication *)[AtomApplication sharedApplication];
|
||||
return [self initWithBootstrapScript:@"window-bootstrap" background:NO alwaysUseBundleResourcePath:false];
|
||||
}
|
||||
|
||||
@@ -119,11 +123,13 @@
|
||||
// have the correct initial size based on the frame's last stored size.
|
||||
// HACK: I hate this and want to place this code directly in windowDidLoad
|
||||
- (void)attachWebView {
|
||||
NSURL *url = [[NSBundle mainBundle] resourceURL];
|
||||
NSURL *url = [[NSBundle bundleForClass:self.class] resourceURL];
|
||||
NSMutableString *urlString = [NSMutableString string];
|
||||
[urlString appendString:[[url URLByAppendingPathComponent:@"static/index.html"] absoluteString]];
|
||||
[urlString appendFormat:@"?bootstrapScript=%@", [self encodeUrlParam:_bootstrapScript]];
|
||||
[urlString appendFormat:@"&resourcePath=%@", [self encodeUrlParam:_resourcePath]];
|
||||
if ([self isDevMode])
|
||||
[urlString appendFormat:@"&devMode=1"];
|
||||
if (_exitWhenDone)
|
||||
[urlString appendString:@"&exitWhenDone=1"];
|
||||
if (_pathToOpen)
|
||||
@@ -202,6 +208,33 @@
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (bool)isDevMode {
|
||||
NSString *bundleResourcePath = [[NSBundle bundleForClass:self.class] resourcePath];
|
||||
return ![_resourcePath isEqualToString:bundleResourcePath];
|
||||
}
|
||||
|
||||
- (void)displayDevIcon {
|
||||
NSView *themeFrame = [self.window.contentView superview];
|
||||
NSButton *fullScreenButton = nil;
|
||||
for (NSView *view in themeFrame.subviews) {
|
||||
if (![view isKindOfClass:NSButton.class]) continue;
|
||||
NSButton *button = (NSButton *)view;
|
||||
if (button.action != @selector(toggleFullScreen:)) continue;
|
||||
fullScreenButton = button;
|
||||
break;
|
||||
}
|
||||
|
||||
NSButton *devButton = [[NSButton alloc] init];
|
||||
[devButton setTitle:@"\xF0\x9F\x92\x80"];
|
||||
devButton.autoresizingMask = NSViewMinXMargin | NSViewMinYMargin;
|
||||
devButton.buttonType = NSMomentaryChangeButton;
|
||||
devButton.bordered = NO;
|
||||
[devButton sizeToFit];
|
||||
devButton.frame = NSMakeRect(fullScreenButton.frame.origin.x - devButton.frame.size.width - 5, fullScreenButton.frame.origin.y, devButton.frame.size.width, devButton.frame.size.height);
|
||||
|
||||
[[self.window.contentView superview] addSubview:devButton];
|
||||
}
|
||||
|
||||
- (void)populateBrowserSettings:(CefBrowserSettings &)settings {
|
||||
CefString(&settings.default_encoding) = "UTF-8";
|
||||
settings.remote_fonts_disabled = false;
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>file.icns</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
28
native/mac/framework-info.plist
Normal file
28
native/mac/framework-info.plist
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string></string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.github.atom.framework</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
5
native/main.cpp
Normal file
5
native/main.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
#include "atom_main.h"
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
return AtomMain(argc, argv);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
#include "include/cef_app.h"
|
||||
#include "atom_cef_app.h"
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
CefMainArgs main_args(argc, argv);
|
||||
CefRefPtr<CefApp> app(new AtomCefApp);
|
||||
return CefExecuteProcess(main_args, app); // Execute the secondary process.
|
||||
}
|
||||
@@ -245,7 +245,7 @@ static NSMutableArray *gPathWatchers;
|
||||
char pathBuffer[MAXPATHLEN];
|
||||
fcntl((int)event.ident, F_GETPATH, &pathBuffer);
|
||||
close(event.ident);
|
||||
newPath = [NSString stringWithUTF8String:pathBuffer];
|
||||
newPath = [[NSString stringWithUTF8String:pathBuffer] stringByStandardizingPath];
|
||||
if (!newPath) {
|
||||
NSLog(@"WARNING: Ignoring rename event for deleted file '%@'", path);
|
||||
continue;
|
||||
|
||||
@@ -22,24 +22,26 @@ namespace v8_extensions {
|
||||
const CefV8ValueList& arguments,
|
||||
CefRefPtr<CefV8Value>& retval,
|
||||
CefString& exception) {
|
||||
CefRefPtr<CefBrowser> browser = CefV8Context::GetCurrentContext()->GetBrowser();
|
||||
@autoreleasepool {
|
||||
CefRefPtr<CefBrowser> browser = CefV8Context::GetCurrentContext()->GetBrowser();
|
||||
|
||||
if (name == "sendMessageToBrowserProcess") {
|
||||
if (arguments.size() == 0 || !arguments[0]->IsString()) {
|
||||
exception = "You must supply a message name";
|
||||
return false;
|
||||
if (name == "sendMessageToBrowserProcess") {
|
||||
if (arguments.size() == 0 || !arguments[0]->IsString()) {
|
||||
exception = "You must supply a message name";
|
||||
return false;
|
||||
}
|
||||
|
||||
CefString name = arguments[0]->GetStringValue();
|
||||
CefRefPtr<CefProcessMessage> message = CefProcessMessage::Create(name);
|
||||
|
||||
if (arguments.size() > 1 && arguments[1]->IsArray()) {
|
||||
TranslateList(arguments[1], message->GetArgumentList());
|
||||
}
|
||||
|
||||
browser->SendProcessMessage(PID_BROWSER, message);
|
||||
return true;
|
||||
}
|
||||
|
||||
CefString name = arguments[0]->GetStringValue();
|
||||
CefRefPtr<CefProcessMessage> message = CefProcessMessage::Create(name);
|
||||
|
||||
if (arguments.size() > 1 && arguments[1]->IsArray()) {
|
||||
TranslateList(arguments[1], message->GetArgumentList());
|
||||
}
|
||||
|
||||
browser->SendProcessMessage(PID_BROWSER, message);
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,6 +8,21 @@ namespace v8_extensions {
|
||||
private:
|
||||
git_repository *repo;
|
||||
|
||||
static int CollectStatus(const char *path, unsigned int status, void *payload) {
|
||||
if ((status & GIT_STATUS_IGNORED) == 0) {
|
||||
std::map<const char*, unsigned int> *statuses = (std::map<const char*, unsigned int> *) payload;
|
||||
statuses->insert(std::pair<const char*, unsigned int>(path, status));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int CollectDiffHunk(const git_diff_delta *delta, const git_diff_range *range,
|
||||
const char *header, size_t header_len, void *payload) {
|
||||
std::vector<git_diff_range> *ranges = (std::vector<git_diff_range> *) payload;
|
||||
ranges->push_back(*range);
|
||||
return 0;
|
||||
}
|
||||
|
||||
public:
|
||||
GitRepository(const char *pathInRepo) {
|
||||
if (git_repository_open_ext(&repo, pathInRepo, 0, NULL) != GIT_OK) {
|
||||
@@ -55,6 +70,126 @@ namespace v8_extensions {
|
||||
return CefV8Value::CreateNull();
|
||||
}
|
||||
|
||||
CefRefPtr<CefV8Value> GetStatuses() {
|
||||
std::map<const char*, unsigned int> statuses;
|
||||
git_status_foreach(repo, CollectStatus, &statuses);
|
||||
std::map<const char*, unsigned int>::iterator iter = statuses.begin();
|
||||
CefRefPtr<CefV8Value> v8Statuses = CefV8Value::CreateObject(NULL);
|
||||
for (; iter != statuses.end(); ++iter) {
|
||||
v8Statuses->SetValue(iter->first, CefV8Value::CreateInt(iter->second), V8_PROPERTY_ATTRIBUTE_NONE);
|
||||
}
|
||||
return v8Statuses;
|
||||
}
|
||||
|
||||
int GetCommitCount(const git_oid* fromCommit, const git_oid* toCommit) {
|
||||
int count = 0;
|
||||
git_revwalk *revWalk;
|
||||
if (git_revwalk_new(&revWalk, repo) == GIT_OK) {
|
||||
git_revwalk_push(revWalk, fromCommit);
|
||||
git_revwalk_hide(revWalk, toCommit);
|
||||
git_oid currentCommit;
|
||||
while (git_revwalk_next(¤tCommit, revWalk) == GIT_OK)
|
||||
count++;
|
||||
git_revwalk_free(revWalk);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void GetShortBranchName(const char** out, const char* branchName) {
|
||||
*out = NULL;
|
||||
if (branchName == NULL)
|
||||
return;
|
||||
int branchNameLength = strlen(branchName);
|
||||
if (branchNameLength < 12)
|
||||
return;
|
||||
if (strncmp("refs/heads/", branchName, 11) != 0)
|
||||
return;
|
||||
|
||||
int shortNameLength = branchNameLength - 11;
|
||||
char* shortName = (char*) malloc(sizeof(char) * (shortNameLength + 1));
|
||||
shortName[shortNameLength] = '\0';
|
||||
strncpy(shortName, &branchName[11], shortNameLength);
|
||||
*out = shortName;
|
||||
}
|
||||
|
||||
void GetUpstreamBranch(const char** out, git_reference *branch) {
|
||||
*out = NULL;
|
||||
|
||||
const char* branchName = git_reference_name(branch);
|
||||
const char* shortBranchName;
|
||||
GetShortBranchName(&shortBranchName, branchName);
|
||||
if (shortBranchName == NULL)
|
||||
return;
|
||||
|
||||
int shortBranchNameLength = strlen(shortBranchName);
|
||||
char* remoteKey = (char*) malloc(sizeof(char) * (shortBranchNameLength + 15));
|
||||
sprintf(remoteKey, "branch.%s.remote", shortBranchName);
|
||||
char* mergeKey = (char*) malloc(sizeof(char) * (shortBranchNameLength + 14));
|
||||
sprintf(mergeKey, "branch.%s.merge", shortBranchName);
|
||||
free((char*)shortBranchName);
|
||||
|
||||
git_config *config;
|
||||
if (git_repository_config(&config, repo) != GIT_OK) {
|
||||
free(remoteKey);
|
||||
free(mergeKey);
|
||||
return;
|
||||
}
|
||||
|
||||
const char *remote;
|
||||
const char *merge;
|
||||
if (git_config_get_string(&remote, config, remoteKey) == GIT_OK
|
||||
&& git_config_get_string(&merge, config, mergeKey) == GIT_OK) {
|
||||
int remoteLength = strlen(remote);
|
||||
if (remoteLength > 0) {
|
||||
const char *shortMergeBranchName;
|
||||
GetShortBranchName(&shortMergeBranchName, merge);
|
||||
if (shortMergeBranchName != NULL) {
|
||||
int updateBranchLength = remoteLength + strlen(shortMergeBranchName) + 14;
|
||||
char* upstreamBranch = (char*) malloc(sizeof(char) * (updateBranchLength + 1));
|
||||
sprintf(upstreamBranch, "refs/remotes/%s/%s", remote, shortMergeBranchName);
|
||||
*out = upstreamBranch;
|
||||
}
|
||||
free((char*)shortMergeBranchName);
|
||||
}
|
||||
}
|
||||
|
||||
free(remoteKey);
|
||||
free(mergeKey);
|
||||
git_config_free(config);
|
||||
}
|
||||
|
||||
CefRefPtr<CefV8Value> GetAheadBehindCounts() {
|
||||
CefRefPtr<CefV8Value> result = NULL;
|
||||
git_reference *head;
|
||||
if (git_repository_head(&head, repo) == GIT_OK) {
|
||||
const char* upstreamBranchName;
|
||||
GetUpstreamBranch(&upstreamBranchName, head);
|
||||
if (upstreamBranchName != NULL) {
|
||||
git_reference *upstream;
|
||||
if (git_reference_lookup(&upstream, repo, upstreamBranchName) == GIT_OK) {
|
||||
const git_oid* headSha = git_reference_target(head);
|
||||
const git_oid* upstreamSha = git_reference_target(upstream);
|
||||
git_oid mergeBase;
|
||||
if (git_merge_base(&mergeBase, repo, headSha, upstreamSha) == GIT_OK) {
|
||||
result = CefV8Value::CreateObject(NULL);
|
||||
int ahead = GetCommitCount(headSha, &mergeBase);
|
||||
result->SetValue("ahead", CefV8Value::CreateInt(ahead), V8_PROPERTY_ATTRIBUTE_NONE);
|
||||
int behind = GetCommitCount(upstreamSha, &mergeBase);
|
||||
result->SetValue("behind", CefV8Value::CreateInt(behind), V8_PROPERTY_ATTRIBUTE_NONE);
|
||||
}
|
||||
git_reference_free(upstream);
|
||||
}
|
||||
free((char*)upstreamBranchName);
|
||||
}
|
||||
git_reference_free(head);
|
||||
}
|
||||
|
||||
if (result != NULL)
|
||||
return result;
|
||||
else
|
||||
return CefV8Value::CreateNull();
|
||||
}
|
||||
|
||||
CefRefPtr<CefV8Value> IsIgnored(const char *path) {
|
||||
int ignored;
|
||||
if (git_ignore_path_is_ignored(&ignored, repo, path) == GIT_OK) {
|
||||
@@ -123,6 +258,7 @@ namespace v8_extensions {
|
||||
|
||||
git_diff_list *diffs;
|
||||
int diffStatus = git_diff_tree_to_workdir(&diffs, repo, tree, &options);
|
||||
git_tree_free(tree);
|
||||
free(copiedPath);
|
||||
if (diffStatus != GIT_OK || git_diff_num_deltas(diffs) != 1) {
|
||||
return CefV8Value::CreateNull();
|
||||
@@ -162,6 +298,58 @@ namespace v8_extensions {
|
||||
return result;
|
||||
}
|
||||
|
||||
CefRefPtr<CefV8Value> GetLineDiffs(const char *path, const char *text) {
|
||||
git_reference *head;
|
||||
if (git_repository_head(&head, repo) != GIT_OK)
|
||||
return CefV8Value::CreateNull();
|
||||
|
||||
const git_oid* sha = git_reference_target(head);
|
||||
git_commit *commit;
|
||||
int commitStatus = git_commit_lookup(&commit, repo, sha);
|
||||
git_reference_free(head);
|
||||
if (commitStatus != GIT_OK)
|
||||
return CefV8Value::CreateNull();
|
||||
|
||||
git_tree *tree;
|
||||
int treeStatus = git_commit_tree(&tree, commit);
|
||||
git_commit_free(commit);
|
||||
if (treeStatus != GIT_OK)
|
||||
return CefV8Value::CreateNull();
|
||||
|
||||
git_tree_entry* treeEntry;
|
||||
git_tree_entry_bypath(&treeEntry, tree, path);
|
||||
git_blob *blob = NULL;
|
||||
if (treeEntry != NULL) {
|
||||
const git_oid *blobSha = git_tree_entry_id(treeEntry);
|
||||
if (blobSha == NULL || git_blob_lookup(&blob, repo, blobSha) != GIT_OK)
|
||||
blob = NULL;
|
||||
}
|
||||
git_tree_free(tree);
|
||||
if (blob == NULL)
|
||||
return CefV8Value::CreateNull();
|
||||
|
||||
int size = strlen(text);
|
||||
std::vector<git_diff_range> ranges;
|
||||
git_diff_options options = GIT_DIFF_OPTIONS_INIT;
|
||||
options.context_lines = 1;
|
||||
if (git_diff_blob_to_buffer(blob, text, size, &options, NULL, CollectDiffHunk, NULL, &ranges) == GIT_OK) {
|
||||
CefRefPtr<CefV8Value> v8Ranges = CefV8Value::CreateArray(ranges.size());
|
||||
for(int i = 0; i < ranges.size(); i++) {
|
||||
CefRefPtr<CefV8Value> v8Range = CefV8Value::CreateObject(NULL);
|
||||
v8Range->SetValue("oldStart", CefV8Value::CreateInt(ranges[i].old_start), V8_PROPERTY_ATTRIBUTE_NONE);
|
||||
v8Range->SetValue("oldLines", CefV8Value::CreateInt(ranges[i].old_lines), V8_PROPERTY_ATTRIBUTE_NONE);
|
||||
v8Range->SetValue("newStart", CefV8Value::CreateInt(ranges[i].new_start), V8_PROPERTY_ATTRIBUTE_NONE);
|
||||
v8Range->SetValue("newLines", CefV8Value::CreateInt(ranges[i].new_lines), V8_PROPERTY_ATTRIBUTE_NONE);
|
||||
v8Ranges->SetValue(i, v8Range);
|
||||
}
|
||||
git_blob_free(blob);
|
||||
return v8Ranges;
|
||||
} else {
|
||||
git_blob_free(blob);
|
||||
return CefV8Value::CreateNull();
|
||||
}
|
||||
}
|
||||
|
||||
CefRefPtr<CefV8Value> IsSubmodule(const char *path) {
|
||||
BOOL isSubmodule = false;
|
||||
git_index* index;
|
||||
@@ -185,12 +373,14 @@ namespace v8_extensions {
|
||||
};
|
||||
|
||||
Git::Git() : CefV8Handler() {
|
||||
git_threads_init();
|
||||
}
|
||||
|
||||
void Git::CreateContextBinding(CefRefPtr<CefV8Context> context) {
|
||||
const char* methodNames[] = {
|
||||
"getRepository", "getHead", "getPath", "isIgnored", "getStatus", "checkoutHead",
|
||||
"getDiffStats", "isSubmodule", "refreshIndex", "destroy"
|
||||
"getDiffStats", "isSubmodule", "refreshIndex", "destroy", "getStatuses",
|
||||
"getAheadBehindCounts", "getLineDiffs"
|
||||
};
|
||||
|
||||
CefRefPtr<CefV8Value> nativeObject = CefV8Value::CreateObject(NULL);
|
||||
@@ -210,72 +400,94 @@ namespace v8_extensions {
|
||||
const CefV8ValueList& arguments,
|
||||
CefRefPtr<CefV8Value>& retval,
|
||||
CefString& exception) {
|
||||
if (name == "getRepository") {
|
||||
GitRepository *repository = new GitRepository(arguments[0]->GetStringValue().ToString().c_str());
|
||||
if (repository->Exists()) {
|
||||
CefRefPtr<CefBase> userData = repository;
|
||||
retval = CefV8Value::CreateObject(NULL);
|
||||
retval->SetUserData(userData);
|
||||
} else {
|
||||
retval = CefV8Value::CreateNull();
|
||||
@autoreleasepool {
|
||||
if (name == "getRepository") {
|
||||
GitRepository *repository = new GitRepository(arguments[0]->GetStringValue().ToString().c_str());
|
||||
if (repository->Exists()) {
|
||||
CefRefPtr<CefBase> userData = repository;
|
||||
retval = CefV8Value::CreateObject(NULL);
|
||||
retval->SetUserData(userData);
|
||||
} else {
|
||||
retval = CefV8Value::CreateNull();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (name == "getHead") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
retval = userData->GetHead();
|
||||
return true;
|
||||
}
|
||||
if (name == "getHead") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
retval = userData->GetHead();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (name == "getPath") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
retval = userData->GetPath();
|
||||
return true;
|
||||
}
|
||||
if (name == "getPath") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
retval = userData->GetPath();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (name == "isIgnored") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
retval = userData->IsIgnored(arguments[0]->GetStringValue().ToString().c_str());
|
||||
return true;
|
||||
}
|
||||
if (name == "isIgnored") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
retval = userData->IsIgnored(arguments[0]->GetStringValue().ToString().c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (name == "getStatus") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
retval = userData->GetStatus(arguments[0]->GetStringValue().ToString().c_str());
|
||||
return true;
|
||||
}
|
||||
if (name == "getStatus") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
retval = userData->GetStatus(arguments[0]->GetStringValue().ToString().c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (name == "checkoutHead") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
retval = userData->CheckoutHead(arguments[0]->GetStringValue().ToString().c_str());
|
||||
return true;
|
||||
}
|
||||
if (name == "checkoutHead") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
retval = userData->CheckoutHead(arguments[0]->GetStringValue().ToString().c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (name == "getDiffStats") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
retval = userData->GetDiffStats(arguments[0]->GetStringValue().ToString().c_str());
|
||||
return true;
|
||||
}
|
||||
if (name == "getDiffStats") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
retval = userData->GetDiffStats(arguments[0]->GetStringValue().ToString().c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (name == "isSubmodule") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
retval = userData->IsSubmodule(arguments[0]->GetStringValue().ToString().c_str());
|
||||
return true;
|
||||
}
|
||||
if (name == "isSubmodule") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
retval = userData->IsSubmodule(arguments[0]->GetStringValue().ToString().c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (name == "refreshIndex") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
userData->RefreshIndex();
|
||||
return true;
|
||||
}
|
||||
if (name == "refreshIndex") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
userData->RefreshIndex();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (name == "destroy") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
userData->Destroy();
|
||||
return true;
|
||||
}
|
||||
if (name == "destroy") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
userData->Destroy();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
if (name == "getStatuses") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
retval = userData->GetStatuses();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (name == "getAheadBehindCounts") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
retval = userData->GetAheadBehindCounts();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (name == "getLineDiffs") {
|
||||
GitRepository *userData = (GitRepository *)object->GetUserData().get();
|
||||
std::string path = arguments[0]->GetStringValue().ToString();
|
||||
std::string text = arguments[1]->GetStringValue().ToString();
|
||||
retval = userData->GetLineDiffs(path.c_str(), text.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace v8_extensions {
|
||||
const CefV8ValueList& arguments,
|
||||
CefRefPtr<CefV8Value>& retval,
|
||||
CefString& exception) {
|
||||
@autoreleasepool {
|
||||
if (name == "exists") {
|
||||
std::string cc_value = arguments[0]->GetStringValue().ToString();
|
||||
const char *path = cc_value.c_str();
|
||||
@@ -165,8 +166,7 @@ namespace v8_extensions {
|
||||
}
|
||||
else if (name == "traverseTree") {
|
||||
std::string argument = arguments[0]->GetStringValue().ToString();
|
||||
int rootPathLength = argument.size() + 1;
|
||||
char rootPath[rootPathLength];
|
||||
char rootPath[argument.size() + 1];
|
||||
strcpy(rootPath, argument.c_str());
|
||||
char * const paths[] = {rootPath, NULL};
|
||||
|
||||
@@ -190,12 +190,8 @@ namespace v8_extensions {
|
||||
continue;
|
||||
}
|
||||
|
||||
int pathLength = entry->fts_pathlen - rootPathLength;
|
||||
char relative[pathLength + 1];
|
||||
relative[pathLength] = '\0';
|
||||
strncpy(relative, entry->fts_path + rootPathLength, pathLength);
|
||||
args.clear();
|
||||
args.push_back(CefV8Value::CreateString(relative));
|
||||
args.push_back(CefV8Value::CreateString(entry->fts_path));
|
||||
if (isFile) {
|
||||
onFile->ExecuteFunction(onFile, args);
|
||||
}
|
||||
@@ -526,10 +522,8 @@ namespace v8_extensions {
|
||||
NSString *word = stringFromCefV8Value(arguments[0]);
|
||||
NSSpellChecker *spellChecker = [NSSpellChecker sharedSpellChecker];
|
||||
@synchronized(spellChecker) {
|
||||
@autoreleasepool {
|
||||
NSRange range = [spellChecker checkSpellingOfString:word startingAt:0];
|
||||
retval = CefV8Value::CreateBool(range.length > 0);
|
||||
}
|
||||
NSRange range = [spellChecker checkSpellingOfString:word startingAt:0];
|
||||
retval = CefV8Value::CreateBool(range.length > 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -538,23 +532,22 @@ namespace v8_extensions {
|
||||
NSString *misspelling = stringFromCefV8Value(arguments[0]);
|
||||
NSSpellChecker *spellChecker = [NSSpellChecker sharedSpellChecker];
|
||||
@synchronized(spellChecker) {
|
||||
@autoreleasepool {
|
||||
NSString *language = [spellChecker language];
|
||||
NSRange range;
|
||||
range.location = 0;
|
||||
range.length = [misspelling length];
|
||||
NSArray *guesses = [spellChecker guessesForWordRange:range inString:misspelling language:language inSpellDocumentWithTag:0];
|
||||
CefRefPtr<CefV8Value> v8Guesses = CefV8Value::CreateArray([guesses count]);
|
||||
for (int i = 0; i < [guesses count]; i++) {
|
||||
v8Guesses->SetValue(i, CefV8Value::CreateString([[guesses objectAtIndex:i] UTF8String]));
|
||||
}
|
||||
retval = v8Guesses;
|
||||
NSString *language = [spellChecker language];
|
||||
NSRange range;
|
||||
range.location = 0;
|
||||
range.length = [misspelling length];
|
||||
NSArray *guesses = [spellChecker guessesForWordRange:range inString:misspelling language:language inSpellDocumentWithTag:0];
|
||||
CefRefPtr<CefV8Value> v8Guesses = CefV8Value::CreateArray([guesses count]);
|
||||
for (int i = 0; i < [guesses count]; i++) {
|
||||
v8Guesses->SetValue(i, CefV8Value::CreateString([[guesses objectAtIndex:i] UTF8String]));
|
||||
}
|
||||
retval = v8Guesses;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
NSString *stringFromCefV8Value(const CefRefPtr<CefV8Value>& value) {
|
||||
|
||||
@@ -73,32 +73,34 @@ bool OnigRegExp::Execute(const CefString& name,
|
||||
CefRefPtr<CefV8Value>& retval,
|
||||
CefString& exception) {
|
||||
|
||||
if (name == "search") {
|
||||
CefRefPtr<CefV8Value> string = arguments[0];
|
||||
CefRefPtr<CefV8Value> index = arguments.size() > 1 ? arguments[1] : CefV8Value::CreateInt(0);
|
||||
OnigRegExpUserData *userData = (OnigRegExpUserData *)object->GetUserData().get();
|
||||
retval = userData->Search(string, index);
|
||||
return true;
|
||||
}
|
||||
else if (name == "test") {
|
||||
CefRefPtr<CefV8Value> string = arguments[0];
|
||||
CefRefPtr<CefV8Value> index = arguments.size() > 1 ? arguments[1] : CefV8Value::CreateInt(0);
|
||||
OnigRegExpUserData *userData = (OnigRegExpUserData *)object->GetUserData().get();
|
||||
retval = userData->Test(string, index);
|
||||
return true;
|
||||
}
|
||||
else if (name == "buildOnigRegExp") {
|
||||
CefRefPtr<CefV8Value> pattern = arguments[0];
|
||||
CefRefPtr<OnigRegExpUserData> userData = new OnigRegExpUserData(pattern);
|
||||
if (!userData->m_regex) {
|
||||
exception = std::string("Failed to create OnigRegExp from pattern '") + pattern->GetStringValue().ToString() + "'";
|
||||
@autoreleasepool {
|
||||
if (name == "search") {
|
||||
CefRefPtr<CefV8Value> string = arguments[0];
|
||||
CefRefPtr<CefV8Value> index = arguments.size() > 1 ? arguments[1] : CefV8Value::CreateInt(0);
|
||||
OnigRegExpUserData *userData = (OnigRegExpUserData *)object->GetUserData().get();
|
||||
retval = userData->Search(string, index);
|
||||
return true;
|
||||
}
|
||||
else if (name == "test") {
|
||||
CefRefPtr<CefV8Value> string = arguments[0];
|
||||
CefRefPtr<CefV8Value> index = arguments.size() > 1 ? arguments[1] : CefV8Value::CreateInt(0);
|
||||
OnigRegExpUserData *userData = (OnigRegExpUserData *)object->GetUserData().get();
|
||||
retval = userData->Test(string, index);
|
||||
return true;
|
||||
}
|
||||
else if (name == "buildOnigRegExp") {
|
||||
CefRefPtr<CefV8Value> pattern = arguments[0];
|
||||
CefRefPtr<OnigRegExpUserData> userData = new OnigRegExpUserData(pattern);
|
||||
if (!userData->m_regex) {
|
||||
exception = std::string("Failed to create OnigRegExp from pattern '") + pattern->GetStringValue().ToString() + "'";
|
||||
}
|
||||
retval = CefV8Value::CreateObject(NULL);
|
||||
retval->SetUserData((CefRefPtr<CefBase>)userData);
|
||||
return true;
|
||||
}
|
||||
retval = CefV8Value::CreateObject(NULL);
|
||||
retval->SetUserData((CefRefPtr<CefBase>)userData);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace v8_extensions
|
||||
@@ -152,18 +152,20 @@ bool OnigScanner::Execute(const CefString& name,
|
||||
const CefV8ValueList& arguments,
|
||||
CefRefPtr<CefV8Value>& retval,
|
||||
CefString& exception) {
|
||||
if (name == "findNextMatch") {
|
||||
OnigScannerUserData *userData = (OnigScannerUserData *)object->GetUserData().get();
|
||||
retval = userData->FindNextMatch(arguments[0], arguments[1]);
|
||||
return true;
|
||||
}
|
||||
else if (name == "buildScanner") {
|
||||
retval = CefV8Value::CreateObject(NULL);
|
||||
retval->SetUserData(new OnigScannerUserData(arguments[0]));
|
||||
return true;
|
||||
}
|
||||
@autoreleasepool {
|
||||
if (name == "findNextMatch") {
|
||||
OnigScannerUserData *userData = (OnigScannerUserData *)object->GetUserData().get();
|
||||
retval = userData->FindNextMatch(arguments[0], arguments[1]);
|
||||
return true;
|
||||
}
|
||||
else if (name == "buildScanner") {
|
||||
retval = CefV8Value::CreateObject(NULL);
|
||||
retval->SetUserData(new OnigScannerUserData(arguments[0]));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace v8_extensions
|
||||
|
||||
@@ -37,78 +37,80 @@ namespace v8_extensions {
|
||||
CefRefPtr<CefV8Value>& retval,
|
||||
CefString& exception) {
|
||||
|
||||
if (name == "find") {
|
||||
std::string path = arguments[0]->GetStringValue().ToString();
|
||||
std::string tag = arguments[1]->GetStringValue().ToString();
|
||||
tagFileInfo info;
|
||||
tagFile* tagFile;
|
||||
tagFile = tagsOpen(path.c_str(), &info);
|
||||
if (info.status.opened) {
|
||||
tagEntry entry;
|
||||
std::vector<CefRefPtr<CefV8Value>> entries;
|
||||
if (tagsFind(tagFile, &entry, tag.c_str(), TAG_FULLMATCH | TAG_OBSERVECASE) == TagSuccess) {
|
||||
entries.push_back(ParseEntry(entry));
|
||||
while (tagsFindNext(tagFile, &entry) == TagSuccess) {
|
||||
entries.push_back(ParseEntry(entry));
|
||||
}
|
||||
}
|
||||
|
||||
retval = CefV8Value::CreateArray(entries.size());
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
retval->SetValue(i, entries[i]);
|
||||
}
|
||||
tagsClose(tagFile);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (name == "getAllTagsAsync") {
|
||||
std::string path = arguments[0]->GetStringValue().ToString();
|
||||
CefRefPtr<CefV8Value> callback = arguments[1];
|
||||
CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
|
||||
|
||||
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||||
dispatch_async(queue, ^{
|
||||
@autoreleasepool {
|
||||
if (name == "find") {
|
||||
std::string path = arguments[0]->GetStringValue().ToString();
|
||||
std::string tag = arguments[1]->GetStringValue().ToString();
|
||||
tagFileInfo info;
|
||||
tagFile* tagFile;
|
||||
tagFile = tagsOpen(path.c_str(), &info);
|
||||
std::vector<tagEntry> entries;
|
||||
|
||||
if (info.status.opened) {
|
||||
tagEntry entry;
|
||||
while (tagsNext(tagFile, &entry) == TagSuccess) {
|
||||
entry.name = strdup(entry.name);
|
||||
entry.file = strdup(entry.file);
|
||||
if (entry.address.pattern) {
|
||||
entry.address.pattern = strdup(entry.address.pattern);
|
||||
std::vector<CefRefPtr<CefV8Value>> entries;
|
||||
if (tagsFind(tagFile, &entry, tag.c_str(), TAG_FULLMATCH | TAG_OBSERVECASE) == TagSuccess) {
|
||||
entries.push_back(ParseEntry(entry));
|
||||
while (tagsFindNext(tagFile, &entry) == TagSuccess) {
|
||||
entries.push_back(ParseEntry(entry));
|
||||
}
|
||||
entries.push_back(entry);
|
||||
}
|
||||
|
||||
retval = CefV8Value::CreateArray(entries.size());
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
retval->SetValue(i, entries[i]);
|
||||
}
|
||||
tagsClose(tagFile);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
dispatch_queue_t mainQueue = dispatch_get_main_queue();
|
||||
dispatch_async(mainQueue, ^{
|
||||
context->Enter();
|
||||
CefRefPtr<CefV8Value> v8Tags = CefV8Value::CreateArray(entries.size());
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
v8Tags->SetValue(i, ParseEntry(entries[i]));
|
||||
free((void*)entries[i].name);
|
||||
free((void*)entries[i].file);
|
||||
if (entries[i].address.pattern) {
|
||||
free((void*)entries[i].address.pattern);
|
||||
if (name == "getAllTagsAsync") {
|
||||
std::string path = arguments[0]->GetStringValue().ToString();
|
||||
CefRefPtr<CefV8Value> callback = arguments[1];
|
||||
CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
|
||||
|
||||
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||||
dispatch_async(queue, ^{
|
||||
tagFileInfo info;
|
||||
tagFile* tagFile;
|
||||
tagFile = tagsOpen(path.c_str(), &info);
|
||||
std::vector<tagEntry> entries;
|
||||
|
||||
if (info.status.opened) {
|
||||
tagEntry entry;
|
||||
while (tagsNext(tagFile, &entry) == TagSuccess) {
|
||||
entry.name = strdup(entry.name);
|
||||
entry.file = strdup(entry.file);
|
||||
if (entry.address.pattern) {
|
||||
entry.address.pattern = strdup(entry.address.pattern);
|
||||
}
|
||||
entries.push_back(entry);
|
||||
}
|
||||
tagsClose(tagFile);
|
||||
}
|
||||
CefV8ValueList callbackArgs;
|
||||
callbackArgs.push_back(v8Tags);
|
||||
callback->ExecuteFunction(callback, callbackArgs);
|
||||
context->Exit();
|
||||
});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
dispatch_queue_t mainQueue = dispatch_get_main_queue();
|
||||
dispatch_async(mainQueue, ^{
|
||||
context->Enter();
|
||||
CefRefPtr<CefV8Value> v8Tags = CefV8Value::CreateArray(entries.size());
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
v8Tags->SetValue(i, ParseEntry(entries[i]));
|
||||
free((void*)entries[i].name);
|
||||
free((void*)entries[i].file);
|
||||
if (entries[i].address.pattern) {
|
||||
free((void*)entries[i].address.pattern);
|
||||
}
|
||||
}
|
||||
CefV8ValueList callbackArgs;
|
||||
callbackArgs.push_back(v8Tags);
|
||||
callback->ExecuteFunction(callback, callbackArgs);
|
||||
context->Exit();
|
||||
});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
"version" : "0.0.0",
|
||||
|
||||
"dependencies": {
|
||||
"coffee-script": "1.x",
|
||||
"cson": "1.x"
|
||||
"coffee-script": "1.5"
|
||||
},
|
||||
|
||||
"private": true,
|
||||
|
||||
"scripts": {
|
||||
"preinstall": "true"
|
||||
}
|
||||
|
||||
Submodule prebuilt-cef updated: c24e35c3ed...3ced0be187
21
script/compile-coffee
Executable file
21
script/compile-coffee
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# Because of the way xcodebuild invokes external scripts we need to load
|
||||
# The Setup's environment ourselves. If this isn't done, things like the
|
||||
# node shim won't be able to find the stuff they need.
|
||||
|
||||
node --version > /dev/null 2>&1 || {
|
||||
if [ -e /opt/github/env.sh ]; then
|
||||
source /opt/github/env.sh
|
||||
else
|
||||
# Try Constructicon's PATH.
|
||||
export PATH="/usr/local/Cellar/node/0.8.21/bin:${PATH}"
|
||||
fi
|
||||
}
|
||||
|
||||
INPUT_FILE="${1}"
|
||||
OUTPUT_FILE="${2}"
|
||||
|
||||
node_modules/.bin/coffee -c -p "${INPUT_FILE}" > "${OUTPUT_FILE}"
|
||||
21
script/compile-cson
Executable file
21
script/compile-cson
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# Because of the way xcodebuild invokes external scripts we need to load
|
||||
# The Setup's environment ourselves. If this isn't done, things like the
|
||||
# node shim won't be able to find the stuff they need.
|
||||
|
||||
node --version > /dev/null 2>&1 || {
|
||||
if [ -e /opt/github/env.sh ]; then
|
||||
source /opt/github/env.sh
|
||||
else
|
||||
# Try Constructicon's PATH.
|
||||
export PATH="/usr/local/Cellar/node/0.8.21/bin:${PATH}"
|
||||
fi
|
||||
}
|
||||
|
||||
INPUT_FILE="${1}"
|
||||
OUTPUT_FILE="${2}"
|
||||
|
||||
node_modules/.bin/coffee script/compile-cson.coffee -- "${INPUT_FILE}" "${OUTPUT_FILE}"
|
||||
23
script/compile-cson.coffee
Normal file
23
script/compile-cson.coffee
Normal file
@@ -0,0 +1,23 @@
|
||||
fs = require 'fs'
|
||||
{exec} = require 'child_process'
|
||||
|
||||
inputFile = process.argv[2]
|
||||
unless inputFile?.length > 0
|
||||
console.error("Input file must be first argument")
|
||||
process.exit(1)
|
||||
|
||||
outputFile = process.argv[3]
|
||||
unless outputFile?.length > 0
|
||||
console.error("Output file must be second arguments")
|
||||
process.exit(1)
|
||||
|
||||
contents = fs.readFileSync(inputFile)?.toString() ? ''
|
||||
exec "node_modules/.bin/coffee -bcp #{inputFile}", (error, stdout, stderr) ->
|
||||
if error
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
json = eval(stdout.toString()) ? {}
|
||||
if json isnt Object(json)
|
||||
console.error("CSON file does not contain valid JSON")
|
||||
process.exit(1)
|
||||
fs.writeFileSync(outputFile, JSON.stringify(json, null, 2))
|
||||
9
script/constructicon/prebuild
Executable file
9
script/constructicon/prebuild
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -ex
|
||||
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
export PATH="/usr/local/Cellar/node/0.8.21/bin:/usr/local/bin:${PATH}"
|
||||
|
||||
rake setup-codesigning create-xcode-project
|
||||
@@ -1,41 +1,10 @@
|
||||
#!/bin/sh
|
||||
# This can only be run by xcode or xcodebuild!
|
||||
|
||||
# Because of the way xcodebuild invokes external scripts we need to load
|
||||
# The Setup's environment ourselves. If this isn't done, things like the
|
||||
# node shim won't be able to find the stuff they need.
|
||||
|
||||
if [ -f /opt/github/env.sh ]; then
|
||||
source /opt/github/env.sh
|
||||
fi
|
||||
set -e
|
||||
|
||||
COMPILED_SOURCES_DIR="${1}"
|
||||
RESOUCES_PATH="$BUILT_PRODUCTS_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH"
|
||||
|
||||
DIRS="src static vendor"
|
||||
|
||||
# Compile .coffee files into bundle
|
||||
COFFEE_FILES=$(find $DIRS -type file -name '*.coffee')
|
||||
for COFFEE_FILE in $COFFEE_FILES; do
|
||||
JS_FILE=$(echo "$RESOUCES_PATH/$COFFEE_FILE" | sed 's/.coffee/.js/' )
|
||||
OUTPUT_PATH="$RESOUCES_PATH/$(dirname "$COFFEE_FILE")"
|
||||
|
||||
if [ $COFFEE_FILE -nt "$JS_FILE" ]; then
|
||||
mkdir -p "$OUTPUT_PATH"
|
||||
node_modules/.bin/coffee -c -o "$OUTPUT_PATH" "$COFFEE_FILE" || exit 1
|
||||
fi
|
||||
done;
|
||||
|
||||
# Compile .cson files into bundle
|
||||
CSON_FILES=$(find $DIRS -type file -name '*.cson')
|
||||
for CSON_FILE in $CSON_FILES; do
|
||||
JSON_FILE=$(echo "$RESOUCES_PATH/$CSON_FILE" | sed 's/.cson/.json/' )
|
||||
OUTPUT_PATH="$RESOUCES_PATH/$(dirname "$CSON_FILE")"
|
||||
|
||||
if [ $CSON_FILE -nt "$JSON_FILE" ]; then
|
||||
mkdir -p "$OUTPUT_PATH"
|
||||
node_modules/.bin/cson2json "$CSON_FILE" > "$JSON_FILE"
|
||||
fi
|
||||
done;
|
||||
|
||||
# Copy non-coffee files into bundle
|
||||
rsync --archive --recursive --exclude="src/**/*.coffee" --exclude="src/**/*.cson" src static vendor spec benchmark themes dot-atom atom.sh "$RESOUCES_PATH"
|
||||
rsync --archive --recursive --exclude="src/**/*.coffee" --exclude="src/**/*.cson" src static vendor spec benchmark themes dot-atom atom.sh "${COMPILED_SOURCES_DIR}/" "$RESOUCES_PATH"
|
||||
|
||||
32
script/generate-sources-gypi
Executable file
32
script/generate-sources-gypi
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/bin/sh
|
||||
|
||||
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}',"
|
||||
done
|
||||
}
|
||||
|
||||
cat > sources.gypi <<EOF
|
||||
{
|
||||
'variables': {
|
||||
'compiled_sources_dir': '<(INTERMEDIATE_DIR)/atom-resources',
|
||||
'compiled_sources_dir_xcode': '\${INTERMEDIATE_DIR}/atom-resources',
|
||||
'coffee_sources': [
|
||||
$(find_files '*.coffee' | file_list)
|
||||
],
|
||||
'cson_sources': [
|
||||
$(find_files '*.cson' | file_list)
|
||||
],
|
||||
},
|
||||
}
|
||||
EOF
|
||||
@@ -3,7 +3,8 @@
|
||||
# From root of libgit2 repo:
|
||||
# mkdir build
|
||||
# cd build
|
||||
# cmake .. -DCMAKE_INSTALL_PREFIX=~/repos/atom/git2 -DCMAKE_OSX_ARCHITECTURES="i386;x86_64" -DCMAKE_BUILD_TYPE=Release
|
||||
# cmake .. -DCMAKE_INSTALL_PREFIX=~/github/atom/git2 -DCMAKE_OSX_ARCHITECTURES="i386;x86_64" -DCMAKE_BUILD_TYPE=Release -DTHREADSAFE=1 -DBUILD_CLAR=OFF
|
||||
|
||||
# cmake --build . --target install
|
||||
#
|
||||
# From root of atom repo:
|
||||
|
||||
@@ -3,18 +3,31 @@ AtomPackage = require 'atom-package'
|
||||
fs = require 'fs'
|
||||
|
||||
describe "AtomPackage", ->
|
||||
[packageMainModule, pack] = []
|
||||
|
||||
beforeEach ->
|
||||
pack = new AtomPackage(fs.resolve(config.packageDirPaths..., 'package-with-activation-events'))
|
||||
pack.load()
|
||||
|
||||
describe ".load()", ->
|
||||
describe "if the package's metadata has a `deferredDeserializers` array", ->
|
||||
it "requires the package's main module attempting to use deserializers named in the array", ->
|
||||
expect(pack.mainModule).toBeNull()
|
||||
object = deserialize(deserializer: 'Foo', data: "Hello")
|
||||
expect(object.constructor.name).toBe 'Foo'
|
||||
expect(object.data).toBe 'Hello'
|
||||
expect(pack.mainModule).toBeDefined()
|
||||
expect(pack.mainModule.activateCallCount).toBe 0
|
||||
|
||||
describe ".activate()", ->
|
||||
beforeEach ->
|
||||
window.rootView = new RootView
|
||||
packageMainModule = require 'fixtures/packages/package-with-activation-events/main'
|
||||
spyOn(packageMainModule, 'activate').andCallThrough()
|
||||
|
||||
describe "when the package metadata includes activation events", ->
|
||||
[packageMainModule, pack] = []
|
||||
|
||||
beforeEach ->
|
||||
pack = new AtomPackage(fs.resolve(config.packageDirPaths..., 'package-with-activation-events'))
|
||||
packageMainModule = require 'fixtures/packages/package-with-activation-events/main'
|
||||
spyOn(packageMainModule, 'activate').andCallThrough()
|
||||
pack.load()
|
||||
pack.activate()
|
||||
|
||||
it "defers activating the package until an activation event bubbles to the root view", ->
|
||||
expect(packageMainModule.activate).not.toHaveBeenCalled()
|
||||
@@ -23,7 +36,7 @@ describe "AtomPackage", ->
|
||||
|
||||
it "triggers the activation event on all handlers registered during activation", ->
|
||||
rootView.open('sample.js')
|
||||
editor = rootView.getActiveEditor()
|
||||
editor = rootView.getActiveView()
|
||||
eventHandler = jasmine.createSpy("activation-event")
|
||||
editor.command 'activation-event', eventHandler
|
||||
editor.trigger 'activation-event'
|
||||
@@ -44,6 +57,7 @@ describe "AtomPackage", ->
|
||||
|
||||
expect(packageMainModule.activate).not.toHaveBeenCalled()
|
||||
pack.load()
|
||||
pack.activate()
|
||||
expect(packageMainModule.activate).toHaveBeenCalled()
|
||||
|
||||
describe "when the package doesn't have an index.coffee", ->
|
||||
|
||||
@@ -84,26 +84,26 @@ describe "the `atom` global", ->
|
||||
describe "activation", ->
|
||||
it "calls activate on the package main with its previous state", ->
|
||||
pack = window.loadPackage('package-with-module')
|
||||
spyOn(pack.packageMain, 'activate')
|
||||
spyOn(pack.mainModule, 'activate')
|
||||
|
||||
serializedState = rootView.serialize()
|
||||
rootView.deactivate()
|
||||
RootView.deserialize(serializedState)
|
||||
window.loadPackage('package-with-module')
|
||||
|
||||
expect(pack.packageMain.activate).toHaveBeenCalledWith(someNumber: 1)
|
||||
expect(pack.mainModule.activate).toHaveBeenCalledWith(someNumber: 1)
|
||||
|
||||
describe "deactivation", ->
|
||||
it "deactivates and removes the package module from the package module map", ->
|
||||
pack = window.loadPackage('package-with-module')
|
||||
expect(atom.activatedAtomPackages.length).toBe 1
|
||||
spyOn(pack.packageMain, "deactivate").andCallThrough()
|
||||
spyOn(pack.mainModule, "deactivate").andCallThrough()
|
||||
atom.deactivateAtomPackages()
|
||||
expect(pack.packageMain.deactivate).toHaveBeenCalled()
|
||||
expect(pack.mainModule.deactivate).toHaveBeenCalled()
|
||||
expect(atom.activatedAtomPackages.length).toBe 0
|
||||
|
||||
describe "serialization", ->
|
||||
it "uses previous serialization state on unactivated packages", ->
|
||||
it "uses previous serialization state on packages whose activation has been deferred", ->
|
||||
atom.atomPackageStates['package-with-activation-events'] = {previousData: 'exists'}
|
||||
unactivatedPackage = window.loadPackage('package-with-activation-events')
|
||||
activatedPackage = window.loadPackage('package-with-module')
|
||||
@@ -115,7 +115,8 @@ describe "the `atom` global", ->
|
||||
'previousData': 'exists'
|
||||
|
||||
# ensure serialization occurs when the packageis activated
|
||||
unactivatedPackage.activatePackageMain()
|
||||
unactivatedPackage.deferActivation = false
|
||||
unactivatedPackage.activate()
|
||||
expect(atom.serializeAtomPackages()).toEqual
|
||||
'package-with-module':
|
||||
'someNumber': 1
|
||||
@@ -124,8 +125,8 @@ describe "the `atom` global", ->
|
||||
|
||||
it "absorbs exceptions that are thrown by the package module's serialize methods", ->
|
||||
spyOn(console, 'error')
|
||||
window.loadPackage('package-with-module')
|
||||
window.loadPackage('package-with-serialize-error', activateImmediately: true)
|
||||
window.loadPackage('package-with-module', activateImmediately: true)
|
||||
window.loadPackage('package-with-serialize-error', activateImmediately: true)
|
||||
|
||||
packageStates = atom.serializeAtomPackages()
|
||||
expect(packageStates['package-with-module']).toEqual someNumber: 1
|
||||
@@ -141,3 +142,74 @@ describe "the `atom` global", ->
|
||||
|
||||
runs ->
|
||||
expect(versionHandler.argsForCall[0][0]).toMatch /^\d+\.\d+(\.\d+)?$/
|
||||
|
||||
describe "modal native dialogs", ->
|
||||
beforeEach ->
|
||||
spyOn(atom, 'sendMessageToBrowserProcess')
|
||||
atom.sendMessageToBrowserProcess.simulateConfirmation = (buttonText) ->
|
||||
labels = @argsForCall[0][1][2...]
|
||||
callbacks = @argsForCall[0][2]
|
||||
@reset()
|
||||
callbacks[labels.indexOf(buttonText)]()
|
||||
advanceClock 50
|
||||
|
||||
atom.sendMessageToBrowserProcess.simulatePathSelection = (path) ->
|
||||
callback = @argsForCall[0][2]
|
||||
@reset()
|
||||
callback(path)
|
||||
advanceClock 50
|
||||
|
||||
it "only presents one native dialog at a time", ->
|
||||
confirmHandler = jasmine.createSpy("confirmHandler")
|
||||
selectPathHandler = jasmine.createSpy("selectPathHandler")
|
||||
|
||||
atom.confirm "Are you happy?", "really, truly happy?", "Yes", confirmHandler, "No"
|
||||
atom.confirm "Are you happy?", "really, truly happy?", "Yes", confirmHandler, "No"
|
||||
atom.showSaveDialog(selectPathHandler)
|
||||
atom.showSaveDialog(selectPathHandler)
|
||||
|
||||
expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
|
||||
atom.sendMessageToBrowserProcess.simulateConfirmation("Yes")
|
||||
expect(confirmHandler).toHaveBeenCalled()
|
||||
|
||||
expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
|
||||
atom.sendMessageToBrowserProcess.simulateConfirmation("No")
|
||||
|
||||
expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
|
||||
atom.sendMessageToBrowserProcess.simulatePathSelection('/selected/path')
|
||||
expect(selectPathHandler).toHaveBeenCalledWith('/selected/path')
|
||||
selectPathHandler.reset()
|
||||
|
||||
expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
|
||||
|
||||
it "prioritizes dialogs presented as the result of dismissing other dialogs before any previously deferred dialogs", ->
|
||||
atom.confirm "A1", "", "Next", ->
|
||||
atom.confirm "B1", "", "Next", ->
|
||||
atom.confirm "C1", "", "Next", ->
|
||||
atom.confirm "C2", "", "Next", ->
|
||||
atom.confirm "B2", "", "Next", ->
|
||||
atom.confirm "A2", "", "Next", ->
|
||||
|
||||
expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
|
||||
expect(atom.sendMessageToBrowserProcess.argsForCall[0][1][0]).toBe "A1"
|
||||
atom.sendMessageToBrowserProcess.simulateConfirmation('Next')
|
||||
|
||||
expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
|
||||
expect(atom.sendMessageToBrowserProcess.argsForCall[0][1][0]).toBe "B1"
|
||||
atom.sendMessageToBrowserProcess.simulateConfirmation('Next')
|
||||
|
||||
expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
|
||||
expect(atom.sendMessageToBrowserProcess.argsForCall[0][1][0]).toBe "C1"
|
||||
atom.sendMessageToBrowserProcess.simulateConfirmation('Next')
|
||||
|
||||
expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
|
||||
expect(atom.sendMessageToBrowserProcess.argsForCall[0][1][0]).toBe "C2"
|
||||
atom.sendMessageToBrowserProcess.simulateConfirmation('Next')
|
||||
|
||||
expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
|
||||
expect(atom.sendMessageToBrowserProcess.argsForCall[0][1][0]).toBe "B2"
|
||||
atom.sendMessageToBrowserProcess.simulateConfirmation('Next')
|
||||
|
||||
expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
|
||||
expect(atom.sendMessageToBrowserProcess.argsForCall[0][1][0]).toBe "A2"
|
||||
atom.sendMessageToBrowserProcess.simulateConfirmation('Next')
|
||||
|
||||
@@ -180,24 +180,88 @@ describe 'Buffer', ->
|
||||
waitsFor 'change event', ->
|
||||
changeHandler.callCount > 0
|
||||
|
||||
describe ".isModified()", ->
|
||||
it "returns true when user changes buffer", ->
|
||||
describe "modified status", ->
|
||||
it "reports the modified status changing to true or false after the user changes buffer", ->
|
||||
modifiedHandler = jasmine.createSpy("modifiedHandler")
|
||||
buffer.on 'modified-status-changed', modifiedHandler
|
||||
|
||||
expect(buffer.isModified()).toBeFalsy()
|
||||
buffer.insert([0,0], "hi")
|
||||
expect(buffer.isModified()).toBe true
|
||||
|
||||
it "returns false after modified buffer is saved", ->
|
||||
advanceClock(buffer.stoppedChangingDelay)
|
||||
expect(modifiedHandler).toHaveBeenCalledWith(true)
|
||||
|
||||
modifiedHandler.reset()
|
||||
buffer.insert([0,2], "ho")
|
||||
advanceClock(buffer.stoppedChangingDelay)
|
||||
expect(modifiedHandler).not.toHaveBeenCalled()
|
||||
|
||||
modifiedHandler.reset()
|
||||
buffer.undo()
|
||||
buffer.undo()
|
||||
advanceClock(buffer.stoppedChangingDelay)
|
||||
expect(modifiedHandler).toHaveBeenCalledWith(false)
|
||||
|
||||
it "reports the modified status changing to true after the underlying file is deleted", ->
|
||||
buffer.release()
|
||||
filePath = "/tmp/atom-tmp-file"
|
||||
fs.write(filePath, 'delete me')
|
||||
buffer = new Buffer(filePath)
|
||||
modifiedHandler = jasmine.createSpy("modifiedHandler")
|
||||
buffer.on 'modified-status-changed', modifiedHandler
|
||||
|
||||
fs.remove(filePath)
|
||||
|
||||
waitsFor "modified status to change", -> modifiedHandler.callCount
|
||||
runs -> expect(buffer.isModified()).toBe true
|
||||
|
||||
it "reports the modified status changing to false after a modified buffer is saved", ->
|
||||
filePath = "/tmp/atom-tmp-file"
|
||||
fs.write(filePath, '')
|
||||
buffer.release()
|
||||
buffer = new Buffer(filePath)
|
||||
expect(buffer.isModified()).toBe false
|
||||
modifiedHandler = jasmine.createSpy("modifiedHandler")
|
||||
buffer.on 'modified-status-changed', modifiedHandler
|
||||
|
||||
buffer.insert([0,0], "hi")
|
||||
advanceClock(buffer.stoppedChangingDelay)
|
||||
expect(buffer.isModified()).toBe true
|
||||
modifiedHandler.reset()
|
||||
|
||||
buffer.save()
|
||||
|
||||
expect(modifiedHandler).toHaveBeenCalledWith(false)
|
||||
expect(buffer.isModified()).toBe false
|
||||
modifiedHandler.reset()
|
||||
|
||||
buffer.insert([0, 0], 'x')
|
||||
advanceClock(buffer.stoppedChangingDelay)
|
||||
expect(modifiedHandler).toHaveBeenCalledWith(true)
|
||||
expect(buffer.isModified()).toBe true
|
||||
|
||||
it "reports the modified status changing to false after a modified buffer is reloaded", ->
|
||||
filePath = "/tmp/atom-tmp-file"
|
||||
fs.write(filePath, '')
|
||||
buffer.release()
|
||||
buffer = new Buffer(filePath)
|
||||
modifiedHandler = jasmine.createSpy("modifiedHandler")
|
||||
buffer.on 'modified-status-changed', modifiedHandler
|
||||
|
||||
buffer.insert([0,0], "hi")
|
||||
advanceClock(buffer.stoppedChangingDelay)
|
||||
expect(buffer.isModified()).toBe true
|
||||
modifiedHandler.reset()
|
||||
|
||||
buffer.reload()
|
||||
expect(modifiedHandler).toHaveBeenCalledWith(false)
|
||||
expect(buffer.isModified()).toBe false
|
||||
modifiedHandler.reset()
|
||||
|
||||
buffer.insert([0, 0], 'x')
|
||||
advanceClock(buffer.stoppedChangingDelay)
|
||||
expect(modifiedHandler).toHaveBeenCalledWith(true)
|
||||
expect(buffer.isModified()).toBe true
|
||||
|
||||
it "returns false for an empty buffer with no path", ->
|
||||
buffer.release()
|
||||
@@ -1056,61 +1120,30 @@ describe 'Buffer', ->
|
||||
expect(buffer.isEmpty()).toBeFalsy()
|
||||
|
||||
describe "'contents-modified' event", ->
|
||||
describe "when the buffer is deleted", ->
|
||||
it "triggers the contents-modified event", ->
|
||||
delay = buffer.stoppedChangingDelay
|
||||
path = "/tmp/atom-file-to-delete.txt"
|
||||
fs.write(path, 'delete me')
|
||||
bufferToDelete = new Buffer(path)
|
||||
contentsModifiedHandler = jasmine.createSpy("contentsModifiedHandler")
|
||||
bufferToDelete.on 'contents-modified', contentsModifiedHandler
|
||||
it "triggers the 'contents-modified' event with the current modified status when the buffer changes, rate-limiting events with a delay", ->
|
||||
delay = buffer.stoppedChangingDelay
|
||||
contentsModifiedHandler = jasmine.createSpy("contentsModifiedHandler")
|
||||
buffer.on 'contents-modified', contentsModifiedHandler
|
||||
|
||||
expect(bufferToDelete.getPath()).toBe path
|
||||
expect(bufferToDelete.isModified()).toBeFalsy()
|
||||
expect(contentsModifiedHandler).not.toHaveBeenCalled()
|
||||
buffer.insert([0, 0], 'a')
|
||||
expect(contentsModifiedHandler).not.toHaveBeenCalled()
|
||||
|
||||
removeHandler = jasmine.createSpy('removeHandler')
|
||||
bufferToDelete.file.on 'removed', removeHandler
|
||||
fs.remove(path)
|
||||
waitsFor "file to be removed", ->
|
||||
removeHandler.callCount > 0
|
||||
advanceClock(delay / 2)
|
||||
|
||||
runs ->
|
||||
expect(contentsModifiedHandler).toHaveBeenCalledWith(differsFromDisk:true)
|
||||
bufferToDelete.destroy()
|
||||
buffer.insert([0, 0], 'b')
|
||||
expect(contentsModifiedHandler).not.toHaveBeenCalled()
|
||||
|
||||
describe "when the buffer text has been changed", ->
|
||||
it "triggers the contents-modified event 'stoppedChangingDelay' ms after the last buffer change", ->
|
||||
delay = buffer.stoppedChangingDelay
|
||||
contentsModifiedHandler = jasmine.createSpy("contentsModifiedHandler")
|
||||
buffer.on 'contents-modified', contentsModifiedHandler
|
||||
advanceClock(delay / 2)
|
||||
expect(contentsModifiedHandler).not.toHaveBeenCalled()
|
||||
|
||||
buffer.insert([0, 0], 'a')
|
||||
expect(contentsModifiedHandler).not.toHaveBeenCalled()
|
||||
advanceClock(delay / 2)
|
||||
expect(contentsModifiedHandler).toHaveBeenCalledWith(true)
|
||||
|
||||
advanceClock(delay / 2)
|
||||
|
||||
buffer.insert([0, 0], 'b')
|
||||
expect(contentsModifiedHandler).not.toHaveBeenCalled()
|
||||
|
||||
advanceClock(delay / 2)
|
||||
expect(contentsModifiedHandler).not.toHaveBeenCalled()
|
||||
|
||||
advanceClock(delay / 2)
|
||||
expect(contentsModifiedHandler).toHaveBeenCalled()
|
||||
|
||||
it "triggers the contents-modified event with data about whether its contents differ from the contents on disk", ->
|
||||
delay = buffer.stoppedChangingDelay
|
||||
contentsModifiedHandler = jasmine.createSpy("contentsModifiedHandler")
|
||||
buffer.on 'contents-modified', contentsModifiedHandler
|
||||
|
||||
buffer.insert([0, 0], 'a')
|
||||
advanceClock(delay)
|
||||
expect(contentsModifiedHandler).toHaveBeenCalledWith(differsFromDisk:true)
|
||||
|
||||
buffer.delete([[0, 0], [0, 1]], '')
|
||||
advanceClock(delay)
|
||||
expect(contentsModifiedHandler).toHaveBeenCalledWith(differsFromDisk:false)
|
||||
contentsModifiedHandler.reset()
|
||||
buffer.undo()
|
||||
buffer.undo()
|
||||
advanceClock(delay)
|
||||
expect(contentsModifiedHandler).toHaveBeenCalledWith(false)
|
||||
|
||||
describe ".append(text)", ->
|
||||
it "adds text to the end of the buffer", ->
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
Config = require 'config'
|
||||
fs = require 'fs'
|
||||
|
||||
describe "Config", ->
|
||||
@@ -133,3 +134,21 @@ describe "Config", ->
|
||||
expect(fs.isFile(fs.join(config.configDirPath, 'themes/atom-light-ui/package.cson'))).toBeTruthy()
|
||||
expect(fs.isFile(fs.join(config.configDirPath, 'themes/atom-dark-syntax.css'))).toBeTruthy()
|
||||
expect(fs.isFile(fs.join(config.configDirPath, 'themes/atom-light-syntax.css'))).toBeTruthy()
|
||||
|
||||
describe "when the config file is not parseable", ->
|
||||
beforeEach ->
|
||||
config.configDirPath = '/tmp/dot-atom-dir'
|
||||
config.configFilePath = fs.join(config.configDirPath, "config.cson")
|
||||
expect(fs.exists(config.configDirPath)).toBeFalsy()
|
||||
|
||||
afterEach ->
|
||||
fs.remove('/tmp/dot-atom-dir') if fs.exists('/tmp/dot-atom-dir')
|
||||
|
||||
it "logs an error to the console and does not overwrite the config file", ->
|
||||
config.save.reset()
|
||||
spyOn(console, 'error')
|
||||
fs.write(config.configFilePath, "{{{{{")
|
||||
config.loadUserConfig()
|
||||
config.set("hair", "blonde") # trigger a save
|
||||
expect(console.error).toHaveBeenCalled()
|
||||
expect(config.save).not.toHaveBeenCalled()
|
||||
@@ -1,11 +1,12 @@
|
||||
DisplayBuffer = require 'display-buffer'
|
||||
Buffer = require 'buffer'
|
||||
_ = require 'underscore'
|
||||
|
||||
describe "DisplayBuffer", ->
|
||||
[editSession, displayBuffer, buffer, changeHandler, tabLength] = []
|
||||
beforeEach ->
|
||||
tabLength = 2
|
||||
editSession = fixturesProject.buildEditSessionForPath('sample.js', { tabLength })
|
||||
editSession = project.buildEditSession('sample.js', { tabLength })
|
||||
{ buffer, displayBuffer } = editSession
|
||||
changeHandler = jasmine.createSpy 'changeHandler'
|
||||
displayBuffer.on 'changed', changeHandler
|
||||
@@ -55,6 +56,13 @@ describe "DisplayBuffer", ->
|
||||
|
||||
describe "when the buffer changes", ->
|
||||
describe "when buffer lines are updated", ->
|
||||
describe "when whitespace is added after the max line length", ->
|
||||
it "adds whitespace to the end of the current line and wraps an empty line", ->
|
||||
fiftyCharacters = _.multiplyString("x", 50)
|
||||
editSession.buffer.setText(fiftyCharacters)
|
||||
editSession.setCursorBufferPosition([0, 51])
|
||||
editSession.insertText(" ")
|
||||
|
||||
describe "when the update makes a soft-wrapped line shorter than the max line length", ->
|
||||
it "rewraps the line and emits a change event", ->
|
||||
buffer.delete([[6, 24], [6, 42]])
|
||||
@@ -220,7 +228,7 @@ describe "DisplayBuffer", ->
|
||||
editSession2 = null
|
||||
|
||||
beforeEach ->
|
||||
editSession2 = fixturesProject.buildEditSessionForPath('two-hundred.txt')
|
||||
editSession2 = project.buildEditSession('two-hundred.txt')
|
||||
{ buffer, displayBuffer } = editSession2
|
||||
displayBuffer.on 'changed', changeHandler
|
||||
|
||||
|
||||
@@ -9,12 +9,30 @@ describe "EditSession", ->
|
||||
buffer.setText(buffer.getText().replace(/[ ]{2}/g, "\t"))
|
||||
|
||||
beforeEach ->
|
||||
editSession = fixturesProject.buildEditSessionForPath('sample.js', autoIndent: false)
|
||||
editSession = project.buildEditSession('sample.js', autoIndent: false)
|
||||
buffer = editSession.buffer
|
||||
lineLengths = buffer.getLines().map (line) -> line.length
|
||||
|
||||
afterEach ->
|
||||
fixturesProject.destroy()
|
||||
describe "title", ->
|
||||
describe ".getTitle()", ->
|
||||
it "uses the basename of the buffer's path as its title, or 'untitled' if the path is undefined", ->
|
||||
expect(editSession.getTitle()).toBe 'sample.js'
|
||||
buffer.setPath(undefined)
|
||||
expect(editSession.getTitle()).toBe 'untitled'
|
||||
|
||||
describe ".getLongTitle()", ->
|
||||
it "appends the name of the containing directory to the basename of the file", ->
|
||||
expect(editSession.getLongTitle()).toBe 'sample.js - fixtures'
|
||||
buffer.setPath(undefined)
|
||||
expect(editSession.getLongTitle()).toBe 'untitled'
|
||||
|
||||
it "emits 'title-changed' events when the underlying buffer path", ->
|
||||
titleChangedHandler = jasmine.createSpy("titleChangedHandler")
|
||||
editSession.on 'title-changed', titleChangedHandler
|
||||
|
||||
buffer.setPath('/foo/bar/baz.txt')
|
||||
buffer.setPath(undefined)
|
||||
expect(titleChangedHandler.callCount).toBe 2
|
||||
|
||||
describe "cursor", ->
|
||||
describe ".getCursor()", ->
|
||||
@@ -793,12 +811,21 @@ describe "EditSession", ->
|
||||
expect(editSession.indentationForBufferRow(2)).toBe editSession.indentationForBufferRow(1)
|
||||
|
||||
describe "when the preceding does not match an auto-indent pattern", ->
|
||||
it "auto-decreases the indentation of the line to be one level below that of the preceding line", ->
|
||||
editSession.setCursorBufferPosition([3, Infinity])
|
||||
editSession.insertText('\n', autoIndent: true)
|
||||
expect(editSession.indentationForBufferRow(4)).toBe editSession.indentationForBufferRow(3)
|
||||
editSession.insertText(' }', autoIndent: true)
|
||||
expect(editSession.indentationForBufferRow(4)).toBe editSession.indentationForBufferRow(3) - 1
|
||||
describe "when the inserted text is whitespace", ->
|
||||
it "does not auto-decreases the indentation", ->
|
||||
editSession.setCursorBufferPosition([12, 0])
|
||||
editSession.insertText(' ', autoIndent: true)
|
||||
expect(editSession.lineForBufferRow(12)).toBe ' };'
|
||||
editSession.insertText('\t\t', autoIndent: true)
|
||||
expect(editSession.lineForBufferRow(12)).toBe ' \t\t};'
|
||||
|
||||
describe "when the inserted text is non-whitespace", ->
|
||||
it "auto-decreases the indentation of the line to be one level below that of the preceding line", ->
|
||||
editSession.setCursorBufferPosition([3, Infinity])
|
||||
editSession.insertText('\n', autoIndent: true)
|
||||
expect(editSession.indentationForBufferRow(4)).toBe editSession.indentationForBufferRow(3)
|
||||
editSession.insertText(' }', autoIndent: true)
|
||||
expect(editSession.indentationForBufferRow(4)).toBe editSession.indentationForBufferRow(3) - 1
|
||||
|
||||
describe "when the current line does not match an auto-outdent pattern", ->
|
||||
it "leaves the line unchanged", ->
|
||||
@@ -1706,7 +1733,7 @@ describe "EditSession", ->
|
||||
|
||||
it "does not explode if the current language mode has no comment regex", ->
|
||||
editSession.destroy()
|
||||
editSession = fixturesProject.buildEditSessionForPath(null, autoIndent: false)
|
||||
editSession = project.buildEditSession(null, autoIndent: false)
|
||||
editSession.setSelectedBufferRange([[4, 5], [4, 5]])
|
||||
editSession.toggleLineCommentsInSelection()
|
||||
expect(buffer.lineForRow(4)).toBe " while(items.length > 0) {"
|
||||
@@ -1784,7 +1811,7 @@ describe "EditSession", ->
|
||||
expect(editSession.getSelectedBufferRanges()).toEqual [[[1, 6], [1, 6]], [[1, 18], [1, 18]]]
|
||||
|
||||
it "restores selected ranges even when the change occurred in another edit session", ->
|
||||
otherEditSession = fixturesProject.buildEditSessionForPath(editSession.getPath())
|
||||
otherEditSession = project.buildEditSession(editSession.getPath())
|
||||
otherEditSession.setSelectedBufferRange([[2, 2], [3, 3]])
|
||||
otherEditSession.delete()
|
||||
|
||||
@@ -1977,13 +2004,13 @@ describe "EditSession", ->
|
||||
|
||||
describe "soft-tabs detection", ->
|
||||
it "assign soft / hard tabs based on the contents of the buffer, or uses the default if unknown", ->
|
||||
editSession = fixturesProject.buildEditSessionForPath('sample.js', softTabs: false)
|
||||
editSession = project.buildEditSession('sample.js', softTabs: false)
|
||||
expect(editSession.softTabs).toBeTruthy()
|
||||
|
||||
editSession = fixturesProject.buildEditSessionForPath('sample-with-tabs.coffee', softTabs: true)
|
||||
editSession = project.buildEditSession('sample-with-tabs.coffee', softTabs: true)
|
||||
expect(editSession.softTabs).toBeFalsy()
|
||||
|
||||
editSession = fixturesProject.buildEditSessionForPath(null, softTabs: false)
|
||||
editSession = project.buildEditSession(null, softTabs: false)
|
||||
expect(editSession.softTabs).toBeFalsy()
|
||||
|
||||
describe ".indentLevelForLine(line)", ->
|
||||
@@ -2006,6 +2033,19 @@ describe "EditSession", ->
|
||||
editSession.buffer.reload()
|
||||
expect(editSession.getCursorScreenPosition()).toEqual [0,1]
|
||||
|
||||
describe "when the 'grammars-loaded' event is triggered on the syntax global", ->
|
||||
it "reloads the edit session's grammar and re-tokenizes the buffer if it changes", ->
|
||||
editSession.destroy()
|
||||
grammarToReturn = syntax.grammarByFileTypeSuffix('txt')
|
||||
spyOn(syntax, 'grammarForFilePath').andCallFake -> grammarToReturn
|
||||
|
||||
editSession = project.buildEditSession('sample.js', autoIndent: false)
|
||||
expect(editSession.lineForScreenRow(0).tokens.length).toBe 1
|
||||
|
||||
grammarToReturn = syntax.grammarByFileTypeSuffix('js')
|
||||
syntax.trigger 'grammars-loaded'
|
||||
expect(editSession.lineForScreenRow(0).tokens.length).toBeGreaterThan 1
|
||||
|
||||
describe "auto-indent", ->
|
||||
describe "editor.autoIndent", ->
|
||||
it "auto-indents newlines if editor.autoIndent is true", ->
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
RootView = require 'root-view'
|
||||
EditSession = require 'edit-session'
|
||||
Buffer = require 'buffer'
|
||||
Editor = require 'editor'
|
||||
@@ -10,75 +9,41 @@ _ = require 'underscore'
|
||||
fs = require 'fs'
|
||||
|
||||
describe "Editor", ->
|
||||
[buffer, editor, cachedLineHeight] = []
|
||||
[buffer, editor, editSession, cachedLineHeight, cachedCharWidth] = []
|
||||
|
||||
beforeEach ->
|
||||
editSession = project.buildEditSession('sample.js')
|
||||
buffer = editSession.buffer
|
||||
editor = new Editor(editSession)
|
||||
editor.lineOverdraw = 2
|
||||
editor.isFocused = true
|
||||
editor.enableKeymap()
|
||||
editor.attachToDom = ({ heightInLines, widthInChars } = {}) ->
|
||||
heightInLines ?= this.getBuffer().getLineCount()
|
||||
this.height(getLineHeight() * heightInLines)
|
||||
this.width(getCharWidth() * widthInChars) if widthInChars
|
||||
$('#jasmine-content').append(this)
|
||||
|
||||
getLineHeight = ->
|
||||
return cachedLineHeight if cachedLineHeight?
|
||||
editorForMeasurement = new Editor(editSession: project.buildEditSessionForPath('sample.js'))
|
||||
editorForMeasurement.attachToDom()
|
||||
cachedLineHeight = editorForMeasurement.lineHeight
|
||||
editorForMeasurement.remove()
|
||||
calcDimensions()
|
||||
cachedLineHeight
|
||||
|
||||
beforeEach ->
|
||||
window.rootView = new RootView
|
||||
rootView.open('sample.js')
|
||||
editor = rootView.getActiveEditor()
|
||||
buffer = editor.getBuffer()
|
||||
getCharWidth = ->
|
||||
return cachedCharWidth if cachedCharWidth?
|
||||
calcDimensions()
|
||||
cachedCharWidth
|
||||
|
||||
editor.attachToDom = ({ heightInLines } = {}) ->
|
||||
heightInLines ?= this.getBuffer().getLineCount()
|
||||
this.height(getLineHeight() * heightInLines)
|
||||
$('#jasmine-content').append(this)
|
||||
|
||||
editor.lineOverdraw = 2
|
||||
editor.enableKeymap()
|
||||
editor.isFocused = true
|
||||
calcDimensions = ->
|
||||
editorForMeasurement = new Editor(editSession: project.buildEditSession('sample.js'))
|
||||
editorForMeasurement.attachToDom()
|
||||
cachedLineHeight = editorForMeasurement.lineHeight
|
||||
cachedCharWidth = editorForMeasurement.charWidth
|
||||
editorForMeasurement.remove()
|
||||
|
||||
describe "construction", ->
|
||||
it "throws an error if no editor session is given unless deserializing", ->
|
||||
it "throws an error if no edit session is given", ->
|
||||
expect(-> new Editor).toThrow()
|
||||
expect(-> new Editor(deserializing: true)).not.toThrow()
|
||||
|
||||
describe ".copy()", ->
|
||||
it "builds a new editor with the same edit sessions, cursor position, and scroll position as the receiver", ->
|
||||
rootView.attachToDom()
|
||||
rootView.height(8 * editor.lineHeight)
|
||||
rootView.width(50 * editor.charWidth)
|
||||
|
||||
editor.edit(project.buildEditSessionForPath('two-hundred.txt'))
|
||||
editor.setCursorScreenPosition([5, 1])
|
||||
editor.scrollTop(1.5 * editor.lineHeight)
|
||||
editor.scrollView.scrollLeft(44)
|
||||
|
||||
# proves this test covers serialization and deserialization
|
||||
spyOn(editor, 'serialize').andCallThrough()
|
||||
spyOn(Editor, 'deserialize').andCallThrough()
|
||||
|
||||
newEditor = editor.copy()
|
||||
expect(editor.serialize).toHaveBeenCalled()
|
||||
expect(Editor.deserialize).toHaveBeenCalled()
|
||||
|
||||
expect(newEditor.getBuffer()).toBe editor.getBuffer()
|
||||
expect(newEditor.getCursorScreenPosition()).toEqual editor.getCursorScreenPosition()
|
||||
expect(newEditor.editSessions).toEqual(editor.editSessions)
|
||||
expect(newEditor.activeEditSession).toEqual(editor.activeEditSession)
|
||||
expect(newEditor.getActiveEditSessionIndex()).toEqual(editor.getActiveEditSessionIndex())
|
||||
|
||||
newEditor.height(editor.height())
|
||||
newEditor.width(editor.width())
|
||||
|
||||
newEditor.attachToDom()
|
||||
expect(newEditor.scrollTop()).toBe editor.scrollTop()
|
||||
expect(newEditor.scrollView.scrollLeft()).toBe 44
|
||||
|
||||
it "does not blow up if no file exists for a previous edit session, but prints a warning", ->
|
||||
spyOn(console, 'warn')
|
||||
fs.write('/tmp/delete-me')
|
||||
editor.edit(project.buildEditSessionForPath('/tmp/delete-me'))
|
||||
fs.remove('/tmp/delete-me')
|
||||
newEditor = editor.copy()
|
||||
expect(console.warn).toHaveBeenCalled()
|
||||
|
||||
describe "when the editor is attached to the dom", ->
|
||||
it "calculates line height and char width and updates the pixel position of the cursor", ->
|
||||
@@ -117,7 +82,7 @@ describe "Editor", ->
|
||||
it "triggers an alert", ->
|
||||
path = "/tmp/atom-changed-file.txt"
|
||||
fs.write(path, "")
|
||||
editSession = project.buildEditSessionForPath(path)
|
||||
editSession = project.buildEditSession(path)
|
||||
editor.edit(editSession)
|
||||
editor.insertText("now the buffer is modified")
|
||||
|
||||
@@ -135,272 +100,66 @@ describe "Editor", ->
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
|
||||
describe ".remove()", ->
|
||||
it "removes subscriptions from all edit session buffers", ->
|
||||
previousEditSession = editor.activeEditSession
|
||||
otherEditSession = project.buildEditSessionForPath(project.resolve('sample.txt'))
|
||||
expect(previousEditSession.buffer.subscriptionCount()).toBeGreaterThan 1
|
||||
|
||||
editor.edit(otherEditSession)
|
||||
expect(otherEditSession.buffer.subscriptionCount()).toBeGreaterThan 1
|
||||
|
||||
it "destroys the edit session", ->
|
||||
editor.remove()
|
||||
expect(previousEditSession.buffer.subscriptionCount()).toBe 0
|
||||
expect(otherEditSession.buffer.subscriptionCount()).toBe 0
|
||||
|
||||
describe "when 'close' is triggered", ->
|
||||
it "adds a closed session path to the array", ->
|
||||
editor.edit(project.buildEditSessionForPath())
|
||||
editSession = editor.activeEditSession
|
||||
expect(editor.closedEditSessions.length).toBe 0
|
||||
editor.trigger "core:close"
|
||||
expect(editor.closedEditSessions.length).toBe 0
|
||||
editor.edit(project.buildEditSessionForPath(project.resolve('sample.txt')))
|
||||
editor.trigger "core:close"
|
||||
expect(editor.closedEditSessions.length).toBe 1
|
||||
|
||||
it "closes the active edit session and loads next edit session", ->
|
||||
editor.edit(project.buildEditSessionForPath())
|
||||
editSession = editor.activeEditSession
|
||||
spyOn(editSession.buffer, 'isModified').andReturn false
|
||||
spyOn(editSession, 'destroy').andCallThrough()
|
||||
spyOn(editor, "remove").andCallThrough()
|
||||
editor.trigger "core:close"
|
||||
expect(editSession.destroy).toHaveBeenCalled()
|
||||
expect(editor.remove).not.toHaveBeenCalled()
|
||||
expect(editor.getBuffer()).toBe buffer
|
||||
|
||||
it "triggers the 'editor:edit-session-removed' event with the edit session and its former index", ->
|
||||
editor.edit(project.buildEditSessionForPath())
|
||||
editSession = editor.activeEditSession
|
||||
index = editor.getActiveEditSessionIndex()
|
||||
spyOn(editSession.buffer, 'isModified').andReturn false
|
||||
|
||||
editSessionRemovedHandler = jasmine.createSpy('editSessionRemovedHandler')
|
||||
editor.on 'editor:edit-session-removed', editSessionRemovedHandler
|
||||
editor.trigger "core:close"
|
||||
|
||||
expect(editSessionRemovedHandler).toHaveBeenCalled()
|
||||
expect(editSessionRemovedHandler.argsForCall[0][1..2]).toEqual [editSession, index]
|
||||
|
||||
it "calls remove on the editor if there is one edit session and mini is false", ->
|
||||
editSession = editor.activeEditSession
|
||||
expect(editor.mini).toBeFalsy()
|
||||
expect(editor.editSessions.length).toBe 1
|
||||
spyOn(editor, 'remove').andCallThrough()
|
||||
editor.trigger 'core:close'
|
||||
spyOn(editSession, 'destroy').andCallThrough()
|
||||
expect(editor.remove).toHaveBeenCalled()
|
||||
|
||||
miniEditor = new Editor(mini: true)
|
||||
spyOn(miniEditor, 'remove').andCallThrough()
|
||||
miniEditor.trigger 'core:close'
|
||||
expect(miniEditor.remove).not.toHaveBeenCalled()
|
||||
|
||||
describe "when buffer is modified", ->
|
||||
it "triggers an alert and does not close the session", ->
|
||||
spyOn(editor, 'remove').andCallThrough()
|
||||
spyOn(atom, 'confirm')
|
||||
editor.insertText("I AM CHANGED!")
|
||||
editor.trigger "core:close"
|
||||
expect(editor.remove).not.toHaveBeenCalled()
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
|
||||
it "doesn't trigger an alert if the buffer is opened in multiple sessions", ->
|
||||
spyOn(editor, 'remove').andCallThrough()
|
||||
spyOn(atom, 'confirm')
|
||||
editor.insertText("I AM CHANGED!")
|
||||
editor.splitLeft()
|
||||
editor.trigger "core:close"
|
||||
expect(editor.remove).toHaveBeenCalled()
|
||||
expect(atom.confirm).not.toHaveBeenCalled()
|
||||
expect(editor.activeEditSession.destroyed).toBeTruthy()
|
||||
|
||||
describe ".edit(editSession)", ->
|
||||
otherEditSession = null
|
||||
[newEditSession, newBuffer] = []
|
||||
|
||||
beforeEach ->
|
||||
otherEditSession = project.buildEditSessionForPath()
|
||||
newEditSession = project.buildEditSession('two-hundred.txt')
|
||||
newBuffer = newEditSession.buffer
|
||||
|
||||
describe "when the edit session wasn't previously assigned to this editor", ->
|
||||
it "adds edit session to editor and triggers the 'editor:edit-session-added' event", ->
|
||||
editSessionAddedHandler = jasmine.createSpy('editSessionAddedHandler')
|
||||
editor.on 'editor:edit-session-added', editSessionAddedHandler
|
||||
it "updates the rendered lines, cursors, selections, scroll position, and event subscriptions to match the given edit session", ->
|
||||
editor.attachToDom(heightInLines: 5, widthInChars: 30)
|
||||
editor.setCursorBufferPosition([3, 5])
|
||||
editor.scrollToBottom()
|
||||
editor.scrollView.scrollLeft(150)
|
||||
previousScrollHeight = editor.verticalScrollbar.prop('scrollHeight')
|
||||
previousScrollTop = editor.scrollTop()
|
||||
previousScrollLeft = editor.scrollView.scrollLeft()
|
||||
|
||||
originalEditSessionCount = editor.editSessions.length
|
||||
editor.edit(otherEditSession)
|
||||
expect(editor.activeEditSession).toBe otherEditSession
|
||||
expect(editor.editSessions.length).toBe originalEditSessionCount + 1
|
||||
newEditSession.scrollTop = 120
|
||||
newEditSession.setSelectedBufferRange([[40, 0], [43, 1]])
|
||||
|
||||
expect(editSessionAddedHandler).toHaveBeenCalled()
|
||||
expect(editSessionAddedHandler.argsForCall[0][1..2]).toEqual [otherEditSession, originalEditSessionCount]
|
||||
editor.edit(newEditSession)
|
||||
{ firstRenderedScreenRow, lastRenderedScreenRow } = editor
|
||||
expect(editor.lineElementForScreenRow(firstRenderedScreenRow).text()).toBe newBuffer.lineForRow(firstRenderedScreenRow)
|
||||
expect(editor.lineElementForScreenRow(lastRenderedScreenRow).text()).toBe newBuffer.lineForRow(editor.lastRenderedScreenRow)
|
||||
expect(editor.scrollTop()).toBe 120
|
||||
expect(editor.getSelectionView().regions[0].position().top).toBe 40 * editor.lineHeight
|
||||
editor.insertText("hello")
|
||||
expect(editor.lineElementForScreenRow(40).text()).toBe "hello3"
|
||||
|
||||
describe "when the edit session was previously assigned to this editor", ->
|
||||
it "restores the previous edit session associated with the editor", ->
|
||||
previousEditSession = editor.activeEditSession
|
||||
editor.edit(editSession)
|
||||
{ firstRenderedScreenRow, lastRenderedScreenRow } = editor
|
||||
expect(editor.lineElementForScreenRow(firstRenderedScreenRow).text()).toBe buffer.lineForRow(firstRenderedScreenRow)
|
||||
expect(editor.lineElementForScreenRow(lastRenderedScreenRow).text()).toBe buffer.lineForRow(editor.lastRenderedScreenRow)
|
||||
expect(editor.verticalScrollbar.prop('scrollHeight')).toBe previousScrollHeight
|
||||
expect(editor.scrollTop()).toBe previousScrollTop
|
||||
expect(editor.scrollView.scrollLeft()).toBe previousScrollLeft
|
||||
expect(editor.getCursorView().position()).toEqual { top: 3 * editor.lineHeight, left: 5 * editor.charWidth }
|
||||
editor.insertText("goodbye")
|
||||
expect(editor.lineElementForScreenRow(3).text()).toMatch /^ vgoodbyear/
|
||||
|
||||
editor.edit(otherEditSession)
|
||||
expect(editor.activeEditSession).not.toBe previousEditSession
|
||||
it "triggers alert if edit session's buffer goes into conflict with changes on disk", ->
|
||||
path = "/tmp/atom-changed-file.txt"
|
||||
fs.write(path, "")
|
||||
tempEditSession = project.buildEditSession(path)
|
||||
editor.edit(tempEditSession)
|
||||
tempEditSession.insertText("a buffer change")
|
||||
|
||||
editor.edit(previousEditSession)
|
||||
expect(editor.activeEditSession).toBe previousEditSession
|
||||
spyOn(atom, "confirm")
|
||||
|
||||
it "handles buffer manipulation correctly after switching to a new edit session", ->
|
||||
editor.attachToDom()
|
||||
editor.insertText("abc\n")
|
||||
expect(editor.lineElementForScreenRow(0).text()).toBe 'abc'
|
||||
contentsConflictedHandler = jasmine.createSpy("contentsConflictedHandler")
|
||||
tempEditSession.on 'contents-conflicted', contentsConflictedHandler
|
||||
fs.write(path, "a file change")
|
||||
waitsFor ->
|
||||
contentsConflictedHandler.callCount > 0
|
||||
|
||||
editor.edit(otherEditSession)
|
||||
expect(editor.lineElementForScreenRow(0).html()).toBe ' '
|
||||
|
||||
editor.insertText("def\n")
|
||||
expect(editor.lineElementForScreenRow(0).text()).toBe 'def'
|
||||
|
||||
it "removes the opened session from the closed sessions array", ->
|
||||
editor.edit(project.buildEditSessionForPath('sample.txt'))
|
||||
expect(editor.closedEditSessions.length).toBe 0
|
||||
editor.trigger "core:close"
|
||||
expect(editor.closedEditSessions.length).toBe 1
|
||||
editor.edit(project.buildEditSessionForPath('sample.txt'))
|
||||
expect(editor.closedEditSessions.length).toBe 0
|
||||
|
||||
describe "switching edit sessions", ->
|
||||
[session0, session1, session2] = []
|
||||
|
||||
beforeEach ->
|
||||
session0 = editor.activeEditSession
|
||||
|
||||
editor.edit(project.buildEditSessionForPath('sample.txt'))
|
||||
session1 = editor.activeEditSession
|
||||
|
||||
editor.edit(project.buildEditSessionForPath('two-hundred.txt'))
|
||||
session2 = editor.activeEditSession
|
||||
|
||||
describe ".setActiveEditSessionIndex(index)", ->
|
||||
it "restores the buffer, cursors, selections, and scroll position of the edit session associated with the index", ->
|
||||
editor.attachToDom(heightInLines: 10)
|
||||
editor.setSelectedBufferRange([[40, 0], [43, 1]])
|
||||
expect(editor.getSelection().getScreenRange()).toEqual [[40, 0], [43, 1]]
|
||||
previousScrollHeight = editor.verticalScrollbar.prop('scrollHeight')
|
||||
|
||||
editor.scrollTop(750)
|
||||
expect(editor.scrollTop()).toBe 750
|
||||
|
||||
editor.setActiveEditSessionIndex(0)
|
||||
expect(editor.getBuffer()).toBe session0.buffer
|
||||
|
||||
editor.setActiveEditSessionIndex(2)
|
||||
expect(editor.getBuffer()).toBe session2.buffer
|
||||
expect(editor.getCursorScreenPosition()).toEqual [43, 1]
|
||||
expect(editor.verticalScrollbar.prop('scrollHeight')).toBe previousScrollHeight
|
||||
expect(editor.scrollTop()).toBe 750
|
||||
expect(editor.getSelection().getScreenRange()).toEqual [[40, 0], [43, 1]]
|
||||
expect(editor.getSelectionView().find('.region')).toExist()
|
||||
|
||||
editor.setActiveEditSessionIndex(0)
|
||||
editor.activeEditSession.selectToEndOfLine()
|
||||
expect(editor.getSelectionView().find('.region')).toExist()
|
||||
|
||||
it "triggers alert if edit session's buffer goes into conflict with changes on disk", ->
|
||||
path = "/tmp/atom-changed-file.txt"
|
||||
fs.write(path, "")
|
||||
editSession = project.buildEditSessionForPath(path)
|
||||
editor.edit editSession
|
||||
editSession.insertText("a buffer change")
|
||||
|
||||
spyOn(atom, "confirm")
|
||||
|
||||
contentsConflictedHandler = jasmine.createSpy("contentsConflictedHandler")
|
||||
editSession.on 'contents-conflicted', contentsConflictedHandler
|
||||
fs.write(path, "a file change")
|
||||
waitsFor ->
|
||||
contentsConflictedHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
|
||||
it "emits an editor:active-edit-session-changed event with the edit session and its index", ->
|
||||
activeEditSessionChangeHandler = jasmine.createSpy('activeEditSessionChangeHandler')
|
||||
editor.on 'editor:active-edit-session-changed', activeEditSessionChangeHandler
|
||||
|
||||
editor.setActiveEditSessionIndex(2)
|
||||
expect(activeEditSessionChangeHandler).toHaveBeenCalled()
|
||||
expect(activeEditSessionChangeHandler.argsForCall[0][1..2]).toEqual [editor.activeEditSession, 2]
|
||||
activeEditSessionChangeHandler.reset()
|
||||
|
||||
editor.setActiveEditSessionIndex(0)
|
||||
expect(activeEditSessionChangeHandler.argsForCall[0][1..2]).toEqual [editor.activeEditSession, 0]
|
||||
activeEditSessionChangeHandler.reset()
|
||||
|
||||
describe ".loadNextEditSession()", ->
|
||||
it "loads the next editor state and wraps to beginning when end is reached", ->
|
||||
expect(editor.activeEditSession).toBe session2
|
||||
editor.loadNextEditSession()
|
||||
expect(editor.activeEditSession).toBe session0
|
||||
editor.loadNextEditSession()
|
||||
expect(editor.activeEditSession).toBe session1
|
||||
editor.loadNextEditSession()
|
||||
expect(editor.activeEditSession).toBe session2
|
||||
|
||||
describe ".loadPreviousEditSession()", ->
|
||||
it "loads the next editor state and wraps to beginning when end is reached", ->
|
||||
expect(editor.activeEditSession).toBe session2
|
||||
editor.loadPreviousEditSession()
|
||||
expect(editor.activeEditSession).toBe session1
|
||||
editor.loadPreviousEditSession()
|
||||
expect(editor.activeEditSession).toBe session0
|
||||
editor.loadPreviousEditSession()
|
||||
expect(editor.activeEditSession).toBe session2
|
||||
|
||||
describe ".save()", ->
|
||||
describe "when the current buffer has a path", ->
|
||||
tempFilePath = null
|
||||
|
||||
beforeEach ->
|
||||
project.setPath('/tmp')
|
||||
tempFilePath = '/tmp/atom-temp.txt'
|
||||
fs.write(tempFilePath, "")
|
||||
rootView.open(tempFilePath)
|
||||
editor = rootView.getActiveEditor()
|
||||
expect(editor.getPath()).toBe tempFilePath
|
||||
|
||||
afterEach ->
|
||||
expect(fs.remove(tempFilePath))
|
||||
|
||||
it "saves the current buffer to disk", ->
|
||||
editor.getBuffer().setText 'Edited!'
|
||||
expect(fs.read(tempFilePath)).not.toBe "Edited!"
|
||||
|
||||
editor.save()
|
||||
|
||||
expect(fs.exists(tempFilePath)).toBeTruthy()
|
||||
expect(fs.read(tempFilePath)).toBe 'Edited!'
|
||||
|
||||
describe "when the current buffer has no path", ->
|
||||
selectedFilePath = null
|
||||
beforeEach ->
|
||||
editor.edit(project.buildEditSessionForPath())
|
||||
|
||||
expect(editor.getPath()).toBeUndefined()
|
||||
editor.getBuffer().setText 'Save me to a new path'
|
||||
spyOn(atom, 'showSaveDialog').andCallFake (callback) -> callback(selectedFilePath)
|
||||
|
||||
it "presents a 'save as' dialog", ->
|
||||
editor.save()
|
||||
expect(atom.showSaveDialog).toHaveBeenCalled()
|
||||
|
||||
describe "when a path is chosen", ->
|
||||
it "saves the buffer to the chosen path", ->
|
||||
selectedFilePath = '/tmp/temp.txt'
|
||||
|
||||
editor.save()
|
||||
|
||||
expect(fs.exists(selectedFilePath)).toBeTruthy()
|
||||
expect(fs.read(selectedFilePath)).toBe 'Save me to a new path'
|
||||
|
||||
describe "when dialog is cancelled", ->
|
||||
it "does not save the buffer", ->
|
||||
selectedFilePath = null
|
||||
editor.save()
|
||||
expect(fs.exists(selectedFilePath)).toBeFalsy()
|
||||
runs ->
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
|
||||
describe ".scrollTop(n)", ->
|
||||
beforeEach ->
|
||||
@@ -449,29 +208,6 @@ describe "Editor", ->
|
||||
editor.scrollTop(50)
|
||||
expect(editor.scrollTop()).toBe 50
|
||||
|
||||
describe "split methods", ->
|
||||
describe "when inside a pane", ->
|
||||
fakePane = null
|
||||
beforeEach ->
|
||||
fakePane = { splitUp: jasmine.createSpy('splitUp').andReturn({}), remove: -> }
|
||||
spyOn(editor, 'pane').andReturn(fakePane)
|
||||
|
||||
it "calls the corresponding split method on the containing pane with a new editor containing a copy of the active edit session", ->
|
||||
editor.edit project.buildEditSessionForPath("sample.txt")
|
||||
editor.splitUp()
|
||||
expect(fakePane.splitUp).toHaveBeenCalled()
|
||||
[newEditor] = fakePane.splitUp.argsForCall[0]
|
||||
expect(newEditor.editSessions.length).toEqual 1
|
||||
expect(newEditor.activeEditSession.buffer).toBe editor.activeEditSession.buffer
|
||||
newEditor.remove()
|
||||
|
||||
describe "when not inside a pane", ->
|
||||
it "does not split the editor, but doesn't throw an exception", ->
|
||||
editor.splitUp().remove()
|
||||
editor.splitDown().remove()
|
||||
editor.splitLeft().remove()
|
||||
editor.splitRight().remove()
|
||||
|
||||
describe "editor:attached event", ->
|
||||
it 'only triggers an editor:attached event when it is first added to the DOM', ->
|
||||
openHandler = jasmine.createSpy('openHandler')
|
||||
@@ -486,7 +222,7 @@ describe "Editor", ->
|
||||
editor.attachToDom()
|
||||
expect(openHandler).not.toHaveBeenCalled()
|
||||
|
||||
describe "editor-path-changed event", ->
|
||||
describe "editor:path-changed event", ->
|
||||
path = null
|
||||
beforeEach ->
|
||||
path = "/tmp/something.txt"
|
||||
@@ -504,7 +240,7 @@ describe "Editor", ->
|
||||
it "emits event when editor receives a new buffer", ->
|
||||
eventHandler = jasmine.createSpy('eventHandler')
|
||||
editor.on 'editor:path-changed', eventHandler
|
||||
editor.edit(project.buildEditSessionForPath(path))
|
||||
editor.edit(project.buildEditSession(path))
|
||||
expect(eventHandler).toHaveBeenCalled()
|
||||
|
||||
it "stops listening to events on previously set buffers", ->
|
||||
@@ -512,7 +248,7 @@ describe "Editor", ->
|
||||
oldBuffer = editor.getBuffer()
|
||||
editor.on 'editor:path-changed', eventHandler
|
||||
|
||||
editor.edit(project.buildEditSessionForPath(path))
|
||||
editor.edit(project.buildEditSession(path))
|
||||
expect(eventHandler).toHaveBeenCalled()
|
||||
|
||||
eventHandler.reset()
|
||||
@@ -539,30 +275,21 @@ describe "Editor", ->
|
||||
afterEach ->
|
||||
editor.clearFontFamily()
|
||||
|
||||
it "updates the font family on new and existing editors", ->
|
||||
rootView.attachToDom()
|
||||
rootView.height(200)
|
||||
rootView.width(200)
|
||||
|
||||
config.set("editor.fontFamily", "Courier")
|
||||
newEditor = editor.splitRight()
|
||||
|
||||
expect($("head style.editor-font-family").text()).toMatch "{font-family: Courier}"
|
||||
expect(editor.css('font-family')).toBe 'Courier'
|
||||
expect(newEditor.css('font-family')).toBe 'Courier'
|
||||
|
||||
it "updates the font family of editors and recalculates dimensions critical to cursor positioning", ->
|
||||
rootView.attachToDom()
|
||||
rootView.height(200)
|
||||
rootView.width(200)
|
||||
|
||||
editor.attachToDom(12)
|
||||
lineHeightBefore = editor.lineHeight
|
||||
charWidthBefore = editor.charWidth
|
||||
config.set("editor.fontFamily", "Courier")
|
||||
editor.setCursorScreenPosition [5, 6]
|
||||
|
||||
config.set("editor.fontFamily", "PCMyungjo")
|
||||
expect(editor.css('font-family')).toBe 'PCMyungjo'
|
||||
expect($("head style.editor-font-family").text()).toMatch "{font-family: PCMyungjo}"
|
||||
expect(editor.charWidth).not.toBe charWidthBefore
|
||||
expect(editor.getCursorView().position()).toEqual { top: 5 * editor.lineHeight, left: 6 * editor.charWidth }
|
||||
expect(editor.verticalScrollbarContent.height()).toBe buffer.getLineCount() * editor.lineHeight
|
||||
|
||||
newEditor = new Editor(editor.activeEditSession.copy())
|
||||
newEditor.attachToDom()
|
||||
expect(newEditor.css('font-family')).toBe 'PCMyungjo'
|
||||
|
||||
describe "font size", ->
|
||||
beforeEach ->
|
||||
@@ -574,24 +301,9 @@ describe "Editor", ->
|
||||
expect($("head style.font-size").text()).toMatch "{font-size: #{config.get('editor.fontSize')}px}"
|
||||
|
||||
describe "when the font size changes", ->
|
||||
it "updates the font family on new and existing editors", ->
|
||||
rootView.attachToDom()
|
||||
rootView.height(200)
|
||||
rootView.width(200)
|
||||
|
||||
config.set("editor.fontSize", 20)
|
||||
newEditor = editor.splitRight()
|
||||
|
||||
expect($("head style.font-size").text()).toMatch "{font-size: 20px}"
|
||||
expect(editor.css('font-size')).toBe '20px'
|
||||
expect(newEditor.css('font-size')).toBe '20px'
|
||||
|
||||
it "updates the font sizes of editors and recalculates dimensions critical to cursor positioning", ->
|
||||
rootView.attachToDom()
|
||||
rootView.height(200)
|
||||
rootView.width(200)
|
||||
|
||||
config.set("editor.fontSize", 10)
|
||||
editor.attachToDom()
|
||||
lineHeightBefore = editor.lineHeight
|
||||
charWidthBefore = editor.charWidth
|
||||
editor.setCursorScreenPosition [5, 6]
|
||||
@@ -604,10 +316,14 @@ describe "Editor", ->
|
||||
expect(editor.renderedLines.outerHeight()).toBe buffer.getLineCount() * editor.lineHeight
|
||||
expect(editor.verticalScrollbarContent.height()).toBe buffer.getLineCount() * editor.lineHeight
|
||||
|
||||
newEditor = new Editor(editor.activeEditSession.copy())
|
||||
newEditor.attachToDom()
|
||||
expect(editor.css('font-size')).toBe '30px'
|
||||
|
||||
it "updates the position and size of selection regions", ->
|
||||
rootView.attachToDom()
|
||||
config.set("editor.fontSize", 10)
|
||||
editor.setSelectedBufferRange([[5, 2], [5, 7]])
|
||||
editor.attachToDom()
|
||||
|
||||
config.set("editor.fontSize", 30)
|
||||
selectionRegion = editor.find('.region')
|
||||
@@ -617,10 +333,10 @@ describe "Editor", ->
|
||||
expect(selectionRegion.width()).toBe 5 * editor.charWidth
|
||||
|
||||
it "updates the gutter width and font size", ->
|
||||
rootView.attachToDom()
|
||||
config.set("editor.fontSize", 16 * 4)
|
||||
expect(editor.gutter.css('font-size')).toBe "#{16 * 4}px"
|
||||
expect(editor.gutter.width()).toBe(64 + editor.gutter.calculateLineNumberPadding())
|
||||
editor.attachToDom()
|
||||
config.set("editor.fontSize", 20)
|
||||
expect(editor.gutter.css('font-size')).toBe "20px"
|
||||
expect(editor.gutter.width()).toBe(editor.charWidth * 2 + editor.gutter.calculateLineNumberPadding())
|
||||
|
||||
it "updates lines if there are unrendered lines", ->
|
||||
editor.attachToDom(heightInLines: 5)
|
||||
@@ -630,22 +346,25 @@ describe "Editor", ->
|
||||
config.set("editor.fontSize", 10)
|
||||
expect(editor.renderedLines.find(".line").length).toBeGreaterThan originalLineCount
|
||||
|
||||
describe "when the editor is detached", ->
|
||||
describe "when the font size changes while editor is detached", ->
|
||||
it "redraws the editor according to the new font size when it is reattached", ->
|
||||
rootView.attachToDom()
|
||||
rootView.height(200)
|
||||
rootView.width(200)
|
||||
editor.setCursorScreenPosition([4, 2])
|
||||
editor.attachToDom()
|
||||
initialLineHeight = editor.lineHeight
|
||||
initialCharWidth = editor.charWidth
|
||||
initialCursorPosition = editor.getCursorView().position()
|
||||
initialScrollbarHeight = editor.verticalScrollbarContent.height()
|
||||
editor.detach()
|
||||
|
||||
newEditor = editor.splitRight()
|
||||
newEditorParent = newEditor.parent()
|
||||
newEditor.detach()
|
||||
config.set("editor.fontSize", 10)
|
||||
newEditorParent.append(newEditor)
|
||||
expect(editor.lineHeight).toBe initialLineHeight
|
||||
expect(editor.charWidth).toBe initialCharWidth
|
||||
|
||||
expect(newEditor.lineHeight).toBe editor.lineHeight
|
||||
expect(newEditor.charWidth).toBe editor.charWidth
|
||||
expect(newEditor.getCursorView().position()).toEqual editor.getCursorView().position()
|
||||
expect(newEditor.verticalScrollbarContent.height()).toBe editor.verticalScrollbarContent.height()
|
||||
editor.attachToDom()
|
||||
expect(editor.lineHeight).not.toBe initialLineHeight
|
||||
expect(editor.charWidth).not.toBe initialCharWidth
|
||||
expect(editor.getCursorView().position()).not.toEqual initialCursorPosition
|
||||
expect(editor.verticalScrollbarContent.height()).not.toBe initialScrollbarHeight
|
||||
|
||||
describe "mouse events", ->
|
||||
beforeEach ->
|
||||
@@ -1373,7 +1092,7 @@ describe "Editor", ->
|
||||
expect(editor.bufferPositionForScreenPosition(editor.getCursorScreenPosition())).toEqual [3, 60]
|
||||
|
||||
it "does not wrap the lines of any newly assigned buffers", ->
|
||||
otherEditSession = project.buildEditSessionForPath()
|
||||
otherEditSession = project.buildEditSession()
|
||||
otherEditSession.buffer.setText([1..100].join(''))
|
||||
editor.edit(otherEditSession)
|
||||
expect(editor.renderedLines.find('.line').length).toBe(1)
|
||||
@@ -1409,7 +1128,7 @@ describe "Editor", ->
|
||||
expect(editor.getCursorScreenPosition()).toEqual [11, 0]
|
||||
|
||||
it "calls .setSoftWrapColumn() when the editor is attached because now its dimensions are available to calculate it", ->
|
||||
otherEditor = new Editor(editSession: project.buildEditSessionForPath('sample.js'))
|
||||
otherEditor = new Editor(editSession: project.buildEditSession('sample.js'))
|
||||
spyOn(otherEditor, 'setSoftWrapColumn')
|
||||
|
||||
otherEditor.setSoftWrap(true)
|
||||
@@ -1417,6 +1136,7 @@ describe "Editor", ->
|
||||
|
||||
otherEditor.simulateDomAttachment()
|
||||
expect(otherEditor.setSoftWrapColumn).toHaveBeenCalled()
|
||||
otherEditor.remove()
|
||||
|
||||
describe "when some lines at the end of the buffer are not visible on screen", ->
|
||||
beforeEach ->
|
||||
@@ -1705,7 +1425,7 @@ describe "Editor", ->
|
||||
|
||||
describe "when autoscrolling at the end of the document", ->
|
||||
it "renders lines properly", ->
|
||||
editor.edit(project.buildEditSessionForPath('two-hundred.txt'))
|
||||
editor.edit(project.buildEditSession('two-hundred.txt'))
|
||||
editor.attachToDom(heightInLines: 5.5)
|
||||
|
||||
expect(editor.renderedLines.find('.line').length).toBe 8
|
||||
@@ -1716,7 +1436,7 @@ describe "Editor", ->
|
||||
|
||||
describe "when line has a character that could push it to be too tall (regression)", ->
|
||||
it "does renders the line at a consistent height", ->
|
||||
rootView.attachToDom()
|
||||
editor.attachToDom()
|
||||
buffer.insert([0, 0], "–")
|
||||
expect(editor.find('.line:eq(0)').outerHeight()).toBe editor.find('.line:eq(1)').outerHeight()
|
||||
|
||||
@@ -1747,21 +1467,11 @@ describe "Editor", ->
|
||||
expect(editor.find('.line').html()).toBe '<span class="source js"><span class="storage modifier js">var</span></span><span class="invisible">¬</span>'
|
||||
|
||||
it "allows invisible glyphs to be customized via config.editor.invisibles", ->
|
||||
rootView.height(200)
|
||||
rootView.attachToDom()
|
||||
rightEditor = rootView.getActiveEditor()
|
||||
rightEditor.setText(" \t ")
|
||||
leftEditor = rightEditor.splitLeft()
|
||||
|
||||
config.set "editor.showInvisibles", true
|
||||
config.set "editor.invisibles",
|
||||
eol: ";"
|
||||
space: "_"
|
||||
tab: "tab"
|
||||
config.update()
|
||||
|
||||
expect(rightEditor.find(".line:first").text()).toBe "_tab _;"
|
||||
expect(leftEditor.find(".line:first").text()).toBe "_tab _;"
|
||||
editor.setText(" \t ")
|
||||
editor.attachToDom()
|
||||
config.set("editor.showInvisibles", true)
|
||||
config.set("editor.invisibles", eol: ";", space: "_", tab: "tab")
|
||||
expect(editor.find(".line:first").text()).toBe "_tab _;"
|
||||
|
||||
it "displays trailing carriage return using a visible non-empty value", ->
|
||||
editor.setText "a line that ends with a carriage return\r\n"
|
||||
@@ -1986,11 +1696,14 @@ describe "Editor", ->
|
||||
|
||||
describe "when the switching from an edit session for a long buffer to an edit session for a short buffer", ->
|
||||
it "updates the line numbers to reflect the shorter buffer", ->
|
||||
editor.edit(fixturesProject.buildEditSessionForPath(null))
|
||||
emptyEditSession = project.buildEditSession(null)
|
||||
editor.edit(emptyEditSession)
|
||||
expect(editor.gutter.lineNumbers.find('.line-number').length).toBe 1
|
||||
|
||||
editor.setActiveEditSessionIndex(0)
|
||||
editor.setActiveEditSessionIndex(1)
|
||||
editor.edit(editSession)
|
||||
expect(editor.gutter.lineNumbers.find('.line-number').length).toBeGreaterThan 1
|
||||
|
||||
editor.edit(emptyEditSession)
|
||||
expect(editor.gutter.lineNumbers.find('.line-number').length).toBe 1
|
||||
|
||||
describe "when the editor is mini", ->
|
||||
@@ -2120,7 +1833,7 @@ describe "Editor", ->
|
||||
|
||||
describe "folding", ->
|
||||
beforeEach ->
|
||||
editSession = project.buildEditSessionForPath('two-hundred.txt')
|
||||
editSession = project.buildEditSession('two-hundred.txt')
|
||||
buffer = editSession.buffer
|
||||
editor.edit(editSession)
|
||||
editor.attachToDom()
|
||||
@@ -2209,14 +1922,6 @@ describe "Editor", ->
|
||||
editor.scrollTop(0)
|
||||
expect(editor.lineElementForScreenRow(2)).toMatchSelector('.fold.selected')
|
||||
|
||||
describe ".getOpenBufferPaths()", ->
|
||||
it "returns the paths of all non-anonymous buffers with edit sessions on this editor", ->
|
||||
editor.edit(project.buildEditSessionForPath('sample.txt'))
|
||||
editor.edit(project.buildEditSessionForPath('two-hundred.txt'))
|
||||
editor.edit(project.buildEditSessionForPath())
|
||||
paths = editor.getOpenBufferPaths().map (path) -> project.relativize(path)
|
||||
expect(paths).toEqual = ['sample.js', 'sample.txt', 'two-hundred.txt']
|
||||
|
||||
describe "paging up and down", ->
|
||||
beforeEach ->
|
||||
editor.attachToDom()
|
||||
@@ -2258,37 +1963,20 @@ describe "Editor", ->
|
||||
expect(editor.getCursor().getScreenPosition().row).toBe(0)
|
||||
expect(editor.getFirstVisibleScreenRow()).toBe(0)
|
||||
|
||||
describe "when autosave is enabled", ->
|
||||
it "autosaves the current buffer when the editor loses focus or switches edit sessions", ->
|
||||
config.set "editor.autosave", true
|
||||
rootView.attachToDom()
|
||||
editor2 = editor.splitRight()
|
||||
spyOn(editor2.activeEditSession, 'save')
|
||||
|
||||
editor.focus()
|
||||
expect(editor2.activeEditSession.save).toHaveBeenCalled()
|
||||
|
||||
editSession = editor.activeEditSession
|
||||
spyOn(editSession, 'save')
|
||||
rootView.open('sample.txt')
|
||||
expect(editSession.save).toHaveBeenCalled()
|
||||
|
||||
describe ".checkoutHead()", ->
|
||||
[path, originalPathText] = []
|
||||
|
||||
beforeEach ->
|
||||
path = require.resolve('fixtures/git/working-dir/file.txt')
|
||||
path = project.resolve('git/working-dir/file.txt')
|
||||
originalPathText = fs.read(path)
|
||||
rootView.open(path)
|
||||
editor = rootView.getActiveEditor()
|
||||
editor.attachToDom()
|
||||
editor.edit(project.buildEditSession(path))
|
||||
|
||||
afterEach ->
|
||||
fs.write(path, originalPathText)
|
||||
|
||||
it "restores the contents of the editor to the HEAD revision", ->
|
||||
editor.setText('')
|
||||
editor.save()
|
||||
editor.getBuffer().save()
|
||||
|
||||
fileChangeHandler = jasmine.createSpy('fileChange')
|
||||
editor.getBuffer().file.on 'contents-changed', fileChangeHandler
|
||||
@@ -2342,7 +2030,7 @@ describe "Editor", ->
|
||||
|
||||
describe "when clicking below the last line", ->
|
||||
beforeEach ->
|
||||
rootView.attachToDom()
|
||||
editor.attachToDom()
|
||||
|
||||
it "move the cursor to the end of the file", ->
|
||||
expect(editor.getCursorScreenPosition()).toEqual [0,0]
|
||||
@@ -2359,68 +2047,19 @@ describe "Editor", ->
|
||||
editor.underlayer.trigger event
|
||||
expect(editor.getSelection().getScreenRange()).toEqual [[0,0], [12,2]]
|
||||
|
||||
describe ".destroyEditSessionIndex(index)", ->
|
||||
it "prompts to save dirty buffers before closing", ->
|
||||
editor.setText("I'm dirty")
|
||||
rootView.open('sample.txt')
|
||||
expect(editor.getEditSessions().length).toBe 2
|
||||
spyOn(atom, "confirm")
|
||||
editor.destroyEditSessionIndex(0)
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
expect(editor.getEditSessions().length).toBe 2
|
||||
expect(editor.getEditSessions()[0].buffer.isModified()).toBeTruthy()
|
||||
|
||||
describe ".destroyInactiveEditSessions()", ->
|
||||
it "destroys every edit session except the active one", ->
|
||||
rootView.open('sample.txt')
|
||||
cssSession = rootView.open('css.css')
|
||||
rootView.open('coffee.coffee')
|
||||
rootView.open('hello.rb')
|
||||
expect(editor.getEditSessions().length).toBe 5
|
||||
editor.setActiveEditSessionIndex(2)
|
||||
editor.destroyInactiveEditSessions()
|
||||
expect(editor.getActiveEditSessionIndex()).toBe 0
|
||||
expect(editor.getEditSessions().length).toBe 1
|
||||
expect(editor.getEditSessions()[0]).toBe cssSession
|
||||
|
||||
it "prompts to save dirty buffers before destroying", ->
|
||||
editor.setText("I'm dirty")
|
||||
dirtySession = editor.activeEditSession
|
||||
rootView.open('sample.txt')
|
||||
expect(editor.getEditSessions().length).toBe 2
|
||||
spyOn(atom, "confirm")
|
||||
editor.destroyInactiveEditSessions()
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
expect(editor.getEditSessions().length).toBe 2
|
||||
expect(editor.getEditSessions()[0].buffer.isModified()).toBeTruthy()
|
||||
|
||||
describe ".destroyAllEditSessions()", ->
|
||||
it "destroys every edit session", ->
|
||||
rootView.open('sample.txt')
|
||||
rootView.open('css.css')
|
||||
rootView.open('coffee.coffee')
|
||||
rootView.open('hello.rb')
|
||||
expect(editor.getEditSessions().length).toBe 5
|
||||
editor.setActiveEditSessionIndex(2)
|
||||
editor.destroyAllEditSessions()
|
||||
expect(editor.pane()).toBeUndefined()
|
||||
expect(editor.getEditSessions().length).toBe 0
|
||||
|
||||
describe ".reloadGrammar()", ->
|
||||
[path] = []
|
||||
|
||||
beforeEach ->
|
||||
path = "/tmp/grammar-change.txt"
|
||||
fs.write(path, "var i;")
|
||||
rootView.attachToDom()
|
||||
|
||||
afterEach ->
|
||||
project.removeGrammarOverrideForPath(path)
|
||||
fs.remove(path) if fs.exists(path)
|
||||
|
||||
it "updates all the rendered lines when the grammar changes", ->
|
||||
rootView.open(path)
|
||||
editor = rootView.getActiveEditor()
|
||||
editor.edit(project.buildEditSession(path))
|
||||
|
||||
expect(editor.getGrammar().name).toBe 'Plain Text'
|
||||
jsGrammar = syntax.grammarForFilePath('/tmp/js.js')
|
||||
expect(jsGrammar.name).toBe 'JavaScript'
|
||||
@@ -2434,12 +2073,6 @@ describe "Editor", ->
|
||||
expect(line0.tokens.length).toBe 3
|
||||
expect(line0.tokens[0]).toEqual(value: 'var', scopes: ['source.js', 'storage.modifier.js'])
|
||||
|
||||
line0 = editor.renderedLines.find('.line:first')
|
||||
span0 = line0.children('span:eq(0)')
|
||||
expect(span0).toMatchSelector '.source.js'
|
||||
expect(span0.children('span:eq(0)')).toMatchSelector '.storage.modifier.js'
|
||||
expect(span0.children('span:eq(0)').text()).toBe 'var'
|
||||
|
||||
it "doesn't update the rendered lines when the grammar doesn't change", ->
|
||||
expect(editor.getGrammar().name).toBe 'JavaScript'
|
||||
spyOn(editor, 'updateDisplay').andCallThrough()
|
||||
@@ -2449,8 +2082,8 @@ describe "Editor", ->
|
||||
expect(editor.getGrammar().name).toBe 'JavaScript'
|
||||
|
||||
it "emits an editor:grammar-changed event when updated", ->
|
||||
rootView.open(path)
|
||||
editor = rootView.getActiveEditor()
|
||||
editor.edit(project.buildEditSession(path))
|
||||
|
||||
eventHandler = jasmine.createSpy('eventHandler')
|
||||
editor.on('editor:grammar-changed', eventHandler)
|
||||
editor.reloadGrammar()
|
||||
@@ -2768,104 +2401,6 @@ describe "Editor", ->
|
||||
expect(buffer.lineForRow(15)).toBeUndefined()
|
||||
expect(editor.getCursorBufferPosition()).toEqual [13, 0]
|
||||
|
||||
describe ".moveEditSessionToIndex(fromIndex, toIndex)", ->
|
||||
describe "when the edit session moves to a later index", ->
|
||||
it "updates the edit session order", ->
|
||||
jsPath = editor.getPath()
|
||||
rootView.open("sample.txt")
|
||||
txtPath = editor.getPath()
|
||||
expect(editor.editSessions[0].getPath()).toBe jsPath
|
||||
expect(editor.editSessions[1].getPath()).toBe txtPath
|
||||
editor.moveEditSessionToIndex(0, 1)
|
||||
expect(editor.editSessions[0].getPath()).toBe txtPath
|
||||
expect(editor.editSessions[1].getPath()).toBe jsPath
|
||||
|
||||
it "fires an editor:edit-session-order-changed event", ->
|
||||
eventHandler = jasmine.createSpy("eventHandler")
|
||||
rootView.open("sample.txt")
|
||||
editor.on "editor:edit-session-order-changed", eventHandler
|
||||
editor.moveEditSessionToIndex(0, 1)
|
||||
expect(eventHandler).toHaveBeenCalled()
|
||||
|
||||
it "sets the moved session as the editor's active session", ->
|
||||
jsPath = editor.getPath()
|
||||
rootView.open("sample.txt")
|
||||
txtPath = editor.getPath()
|
||||
expect(editor.activeEditSession.getPath()).toBe txtPath
|
||||
editor.moveEditSessionToIndex(0, 1)
|
||||
expect(editor.activeEditSession.getPath()).toBe jsPath
|
||||
|
||||
describe "when the edit session moves to an earlier index", ->
|
||||
it "updates the edit session order", ->
|
||||
jsPath = editor.getPath()
|
||||
rootView.open("sample.txt")
|
||||
txtPath = editor.getPath()
|
||||
expect(editor.editSessions[0].getPath()).toBe jsPath
|
||||
expect(editor.editSessions[1].getPath()).toBe txtPath
|
||||
editor.moveEditSessionToIndex(1, 0)
|
||||
expect(editor.editSessions[0].getPath()).toBe txtPath
|
||||
expect(editor.editSessions[1].getPath()).toBe jsPath
|
||||
|
||||
it "fires an editor:edit-session-order-changed event", ->
|
||||
eventHandler = jasmine.createSpy("eventHandler")
|
||||
rootView.open("sample.txt")
|
||||
editor.on "editor:edit-session-order-changed", eventHandler
|
||||
editor.moveEditSessionToIndex(1, 0)
|
||||
expect(eventHandler).toHaveBeenCalled()
|
||||
|
||||
it "sets the moved session as the editor's active session", ->
|
||||
jsPath = editor.getPath()
|
||||
rootView.open("sample.txt")
|
||||
txtPath = editor.getPath()
|
||||
expect(editor.activeEditSession.getPath()).toBe txtPath
|
||||
editor.moveEditSessionToIndex(1, 0)
|
||||
expect(editor.activeEditSession.getPath()).toBe txtPath
|
||||
|
||||
describe ".moveEditSessionToEditor(fromIndex, toEditor, toIndex)", ->
|
||||
it "closes the edit session in the source editor", ->
|
||||
jsPath = editor.getPath()
|
||||
rootView.open("sample.txt")
|
||||
txtPath = editor.getPath()
|
||||
rightEditor = editor.splitRight()
|
||||
expect(editor.editSessions[0].getPath()).toBe jsPath
|
||||
expect(editor.editSessions[1].getPath()).toBe txtPath
|
||||
editor.moveEditSessionToEditor(0, rightEditor, 1)
|
||||
expect(editor.editSessions[0].getPath()).toBe txtPath
|
||||
expect(editor.editSessions[1]).toBeUndefined()
|
||||
|
||||
it "opens the edit session in the destination editor at the target index", ->
|
||||
jsPath = editor.getPath()
|
||||
rootView.open("sample.txt")
|
||||
txtPath = editor.getPath()
|
||||
rightEditor = editor.splitRight()
|
||||
expect(rightEditor.editSessions[0].getPath()).toBe txtPath
|
||||
expect(rightEditor.editSessions[1]).toBeUndefined()
|
||||
editor.moveEditSessionToEditor(0, rightEditor, 0)
|
||||
expect(rightEditor.editSessions[0].getPath()).toBe jsPath
|
||||
expect(rightEditor.editSessions[1].getPath()).toBe txtPath
|
||||
|
||||
describe "when editor:undo-close-session is triggered", ->
|
||||
describe "when an edit session is opened back up after it is closed", ->
|
||||
it "is removed from the undo stack and not reopened when the event is triggered", ->
|
||||
rootView.open('sample.txt')
|
||||
expect(editor.getPath()).toBe fixturesProject.resolve('sample.txt')
|
||||
editor.trigger "core:close"
|
||||
expect(editor.closedEditSessions.length).toBe 1
|
||||
rootView.open('sample.txt')
|
||||
expect(editor.closedEditSessions.length).toBe 0
|
||||
editor.trigger 'editor:undo-close-session'
|
||||
expect(editor.getPath()).toBe fixturesProject.resolve('sample.txt')
|
||||
|
||||
it "opens the closed session back up at the previous index", ->
|
||||
rootView.open('sample.txt')
|
||||
editor.loadPreviousEditSession()
|
||||
expect(editor.getPath()).toBe fixturesProject.resolve('sample.js')
|
||||
editor.trigger "core:close"
|
||||
expect(editor.getPath()).toBe fixturesProject.resolve('sample.txt')
|
||||
editor.trigger 'editor:undo-close-session'
|
||||
expect(editor.getPath()).toBe fixturesProject.resolve('sample.js')
|
||||
expect(editor.getActiveEditSessionIndex()).toBe 0
|
||||
|
||||
describe "editor:save-debug-snapshot", ->
|
||||
it "saves the state of the rendered lines, the display buffer, and the buffer to a file of the user's choosing", ->
|
||||
saveDialogCallback = null
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
Git = require 'git'
|
||||
fs = require 'fs'
|
||||
Task = require 'task'
|
||||
|
||||
describe "Git", ->
|
||||
repo = null
|
||||
@@ -21,11 +22,11 @@ describe "Git", ->
|
||||
describe ".getPath()", ->
|
||||
it "returns the repository path for a .git directory path", ->
|
||||
repo = new Git(require.resolve('fixtures/git/master.git/HEAD'))
|
||||
expect(repo.getPath()).toBe require.resolve('fixtures/git/master.git') + '/'
|
||||
expect(repo.getPath()).toBe require.resolve('fixtures/git/master.git')
|
||||
|
||||
it "returns the repository path for a repository path", ->
|
||||
repo = new Git(require.resolve('fixtures/git/master.git'))
|
||||
expect(repo.getPath()).toBe require.resolve('fixtures/git/master.git') + '/'
|
||||
expect(repo.getPath()).toBe require.resolve('fixtures/git/master.git')
|
||||
|
||||
describe ".getHead()", ->
|
||||
it "returns a branch name for a non-empty repository", ->
|
||||
@@ -126,6 +127,18 @@ describe "Git", ->
|
||||
expect(fs.read(path2)).toBe('path 2 is edited')
|
||||
expect(repo.isPathModified(path2)).toBeTruthy()
|
||||
|
||||
it "fires a status-changed event if the checkout completes successfully", ->
|
||||
fs.write(path1, '')
|
||||
repo.getPathStatus(path1)
|
||||
statusHandler = jasmine.createSpy('statusHandler')
|
||||
repo.on 'status-changed', statusHandler
|
||||
repo.checkoutHead(path1)
|
||||
expect(statusHandler.callCount).toBe 1
|
||||
expect(statusHandler.argsForCall[0][0..1]).toEqual [path1, 0]
|
||||
|
||||
repo.checkoutHead(path1)
|
||||
expect(statusHandler.callCount).toBe 1
|
||||
|
||||
describe ".destroy()", ->
|
||||
it "throws an exception when any method is called after it is called", ->
|
||||
repo = new Git(require.resolve('fixtures/git/master.git/HEAD'))
|
||||
@@ -147,3 +160,75 @@ describe "Git", ->
|
||||
expect(repo.getDiffStats(path)).toEqual {added: 0, deleted: 0}
|
||||
fs.write(path, "#{originalPathText} edited line")
|
||||
expect(repo.getDiffStats(path)).toEqual {added: 1, deleted: 1}
|
||||
|
||||
describe ".getPathStatus(path)", ->
|
||||
[path, originalPathText] = []
|
||||
|
||||
beforeEach ->
|
||||
repo = new Git(require.resolve('fixtures/git/working-dir'))
|
||||
path = require.resolve('fixtures/git/working-dir/file.txt')
|
||||
originalPathText = fs.read(path)
|
||||
|
||||
afterEach ->
|
||||
fs.write(path, originalPathText)
|
||||
|
||||
it "trigger a status-changed event when the new status differs from the last cached one", ->
|
||||
statusHandler = jasmine.createSpy("statusHandler")
|
||||
repo.on 'status-changed', statusHandler
|
||||
fs.write(path, '')
|
||||
status = repo.getPathStatus(path)
|
||||
expect(statusHandler.callCount).toBe 1
|
||||
expect(statusHandler.argsForCall[0][0..1]).toEqual [path, status]
|
||||
|
||||
fs.write(path, 'abc')
|
||||
status = repo.getPathStatus(path)
|
||||
expect(statusHandler.callCount).toBe 1
|
||||
|
||||
describe ".refreshStatus()", ->
|
||||
[newPath, modifiedPath, cleanPath, originalModifiedPathText] = []
|
||||
|
||||
beforeEach ->
|
||||
repo = new Git(require.resolve('fixtures/git/working-dir'))
|
||||
modifiedPath = project.resolve('git/working-dir/file.txt')
|
||||
originalModifiedPathText = fs.read(modifiedPath)
|
||||
newPath = project.resolve('git/working-dir/untracked.txt')
|
||||
cleanPath = project.resolve('git/working-dir/other.txt')
|
||||
fs.write(newPath, '')
|
||||
|
||||
afterEach ->
|
||||
fs.write(modifiedPath, originalModifiedPathText)
|
||||
fs.remove(newPath) if fs.exists(newPath)
|
||||
|
||||
it "returns status information for all new and modified files", ->
|
||||
fs.write(modifiedPath, 'making this path modified')
|
||||
statusHandler = jasmine.createSpy('statusHandler')
|
||||
repo.on 'statuses-changed', statusHandler
|
||||
repo.refreshStatus()
|
||||
|
||||
waitsFor ->
|
||||
statusHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
statuses = repo.statuses
|
||||
expect(statuses[cleanPath]).toBeUndefined()
|
||||
expect(repo.isStatusNew(statuses[newPath])).toBeTruthy()
|
||||
expect(repo.isStatusModified(statuses[modifiedPath])).toBeTruthy()
|
||||
|
||||
it "only starts a single web worker at a time and schedules a restart if one is already running", =>
|
||||
fs.write(modifiedPath, 'making this path modified')
|
||||
statusHandler = jasmine.createSpy('statusHandler')
|
||||
repo.on 'statuses-changed', statusHandler
|
||||
|
||||
spyOn(Task.prototype, "start").andCallThrough()
|
||||
repo.refreshStatus()
|
||||
expect(Task.prototype.start.callCount).toBe 1
|
||||
repo.refreshStatus()
|
||||
expect(Task.prototype.start.callCount).toBe 1
|
||||
repo.refreshStatus()
|
||||
expect(Task.prototype.start.callCount).toBe 1
|
||||
|
||||
waitsFor ->
|
||||
statusHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
expect(Task.prototype.start.callCount).toBe 2
|
||||
|
||||
@@ -7,10 +7,8 @@ describe "GrammarView", ->
|
||||
|
||||
beforeEach ->
|
||||
window.rootView = new RootView
|
||||
project.removeGrammarOverrideForPath('sample.js')
|
||||
rootView.open('sample.js')
|
||||
editor = rootView.getActiveEditor()
|
||||
rootView.attachToDom()
|
||||
editor = rootView.getActiveView()
|
||||
textGrammar = _.find syntax.grammars, (grammar) -> grammar.name is 'Plain Text'
|
||||
expect(textGrammar).toBeTruthy()
|
||||
jsGrammar = _.find syntax.grammars, (grammar) -> grammar.name is 'JavaScript'
|
||||
|
||||
@@ -10,18 +10,18 @@ describe "LanguageMode", ->
|
||||
|
||||
describe "common behavior", ->
|
||||
beforeEach ->
|
||||
editSession = fixturesProject.buildEditSessionForPath('sample.js', autoIndent: false)
|
||||
editSession = project.buildEditSession('sample.js', autoIndent: false)
|
||||
{ buffer, languageMode } = editSession
|
||||
|
||||
describe "language detection", ->
|
||||
it "uses the file name as the file type if it has no extension", ->
|
||||
jsEditSession = fixturesProject.buildEditSessionForPath('js', autoIndent: false)
|
||||
jsEditSession = project.buildEditSession('js', autoIndent: false)
|
||||
expect(jsEditSession.languageMode.grammar.name).toBe "JavaScript"
|
||||
jsEditSession.destroy()
|
||||
|
||||
describe "javascript", ->
|
||||
beforeEach ->
|
||||
editSession = fixturesProject.buildEditSessionForPath('sample.js', autoIndent: false)
|
||||
editSession = project.buildEditSession('sample.js', autoIndent: false)
|
||||
{ buffer, languageMode } = editSession
|
||||
|
||||
describe ".toggleLineCommentsForBufferRows(start, end)", ->
|
||||
@@ -63,7 +63,7 @@ describe "LanguageMode", ->
|
||||
|
||||
describe "coffeescript", ->
|
||||
beforeEach ->
|
||||
editSession = fixturesProject.buildEditSessionForPath('coffee.coffee', autoIndent: false)
|
||||
editSession = project.buildEditSession('coffee.coffee', autoIndent: false)
|
||||
{ buffer, languageMode } = editSession
|
||||
|
||||
describe ".toggleLineCommentsForBufferRows(start, end)", ->
|
||||
@@ -98,7 +98,7 @@ describe "LanguageMode", ->
|
||||
|
||||
describe "css", ->
|
||||
beforeEach ->
|
||||
editSession = fixturesProject.buildEditSessionForPath('css.css', autoIndent: false)
|
||||
editSession = project.buildEditSession('css.css', autoIndent: false)
|
||||
{ buffer, languageMode } = editSession
|
||||
|
||||
describe ".toggleLineCommentsForBufferRows(start, end)", ->
|
||||
|
||||
192
spec/app/pane-container-spec.coffee
Normal file
192
spec/app/pane-container-spec.coffee
Normal file
@@ -0,0 +1,192 @@
|
||||
PaneContainer = require 'pane-container'
|
||||
Pane = require 'pane'
|
||||
{View, $$} = require 'space-pen'
|
||||
_ = require 'underscore'
|
||||
$ = require 'jquery'
|
||||
|
||||
describe "PaneContainer", ->
|
||||
[TestView, container, pane1, pane2, pane3] = []
|
||||
|
||||
beforeEach ->
|
||||
class TestView extends View
|
||||
registerDeserializer(this)
|
||||
@deserialize: ({name}) -> new TestView(name)
|
||||
@content: -> @div tabindex: -1
|
||||
initialize: (@name) -> @text(@name)
|
||||
serialize: -> { deserializer: 'TestView', @name }
|
||||
getUri: -> "/tmp/#{@name}"
|
||||
save: -> @saved = true
|
||||
isEqual: (other) -> @name is other.name
|
||||
|
||||
container = new PaneContainer
|
||||
pane1 = new Pane(new TestView('1'))
|
||||
container.append(pane1)
|
||||
pane2 = pane1.splitRight(new TestView('2'))
|
||||
pane3 = pane2.splitDown(new TestView('3'))
|
||||
|
||||
afterEach ->
|
||||
unregisterDeserializer(TestView)
|
||||
|
||||
describe ".focusNextPane()", ->
|
||||
it "focuses the pane following the focused pane or the first pane if no pane has focus", ->
|
||||
container.attachToDom()
|
||||
container.focusNextPane()
|
||||
expect(pane1.activeItem).toMatchSelector ':focus'
|
||||
container.focusNextPane()
|
||||
expect(pane2.activeItem).toMatchSelector ':focus'
|
||||
container.focusNextPane()
|
||||
expect(pane3.activeItem).toMatchSelector ':focus'
|
||||
container.focusNextPane()
|
||||
expect(pane1.activeItem).toMatchSelector ':focus'
|
||||
|
||||
describe ".getActivePane()", ->
|
||||
it "returns the most-recently focused pane", ->
|
||||
focusStealer = $$ -> @div tabindex: -1, "focus stealer"
|
||||
focusStealer.attachToDom()
|
||||
container.attachToDom()
|
||||
|
||||
pane2.focus()
|
||||
expect(container.getFocusedPane()).toBe pane2
|
||||
expect(container.getActivePane()).toBe pane2
|
||||
|
||||
focusStealer.focus()
|
||||
expect(container.getFocusedPane()).toBeUndefined()
|
||||
expect(container.getActivePane()).toBe pane2
|
||||
|
||||
pane3.focus()
|
||||
expect(container.getFocusedPane()).toBe pane3
|
||||
expect(container.getActivePane()).toBe pane3
|
||||
|
||||
# returns the first pane if none have been set to active
|
||||
container.find('.pane.active').removeClass('active')
|
||||
expect(container.getActivePane()).toBe pane1
|
||||
|
||||
describe ".eachPane(callback)", ->
|
||||
it "runs the callback with all current and future panes until the subscription is cancelled", ->
|
||||
panes = []
|
||||
subscription = container.eachPane (pane) -> panes.push(pane)
|
||||
expect(panes).toEqual [pane1, pane2, pane3]
|
||||
|
||||
panes = []
|
||||
pane4 = pane3.splitRight()
|
||||
expect(panes).toEqual [pane4]
|
||||
|
||||
panes = []
|
||||
subscription.cancel()
|
||||
pane4.splitDown()
|
||||
expect(panes).toEqual []
|
||||
|
||||
describe ".reopenItem()", ->
|
||||
describe "when there is an active pane", ->
|
||||
it "reconstructs and shows the last-closed pane item", ->
|
||||
expect(container.getActivePane()).toBe pane3
|
||||
item3 = pane3.activeItem
|
||||
item4 = new TestView('4')
|
||||
pane3.showItem(item4)
|
||||
|
||||
pane3.destroyItem(item3)
|
||||
pane3.destroyItem(item4)
|
||||
expect(container.getActivePane()).toBe pane1
|
||||
|
||||
expect(container.reopenItem()).toBeTruthy()
|
||||
expect(pane1.activeItem).toEqual item4
|
||||
|
||||
expect(container.reopenItem()).toBeTruthy()
|
||||
expect(pane1.activeItem).toEqual item3
|
||||
|
||||
expect(container.reopenItem()).toBeFalsy()
|
||||
expect(pane1.activeItem).toEqual item3
|
||||
|
||||
describe "when there is no active pane", ->
|
||||
it "attaches a new pane with the reconstructed last pane item", ->
|
||||
pane1.remove()
|
||||
pane2.remove()
|
||||
item3 = pane3.activeItem
|
||||
pane3.destroyItem(item3)
|
||||
expect(container.getActivePane()).toBeUndefined()
|
||||
|
||||
container.reopenItem()
|
||||
|
||||
expect(container.getActivePane().activeItem).toEqual item3
|
||||
|
||||
it "does not reopen an item that is already open", ->
|
||||
item3 = pane3.activeItem
|
||||
item4 = new TestView('4')
|
||||
pane3.showItem(item4)
|
||||
pane3.destroyItem(item3)
|
||||
pane3.destroyItem(item4)
|
||||
|
||||
expect(container.getActivePane()).toBe pane1
|
||||
pane1.showItem(new TestView('4'))
|
||||
|
||||
expect(container.reopenItem()).toBeTruthy()
|
||||
expect(_.pluck(pane1.getItems(), 'name')).toEqual ['1', '4', '3']
|
||||
expect(pane1.activeItem).toEqual item3
|
||||
|
||||
expect(container.reopenItem()).toBeFalsy()
|
||||
expect(pane1.activeItem).toEqual item3
|
||||
|
||||
describe ".saveAll()", ->
|
||||
it "saves all open pane items", ->
|
||||
pane1.showItem(new TestView('4'))
|
||||
|
||||
container.saveAll()
|
||||
|
||||
for pane in container.getPanes()
|
||||
for item in pane.getItems()
|
||||
expect(item.saved).toBeTruthy()
|
||||
|
||||
describe ".confirmClose()", ->
|
||||
it "resolves the returned promise after modified files are saved", ->
|
||||
pane1.itemAtIndex(0).isModified = -> true
|
||||
pane2.itemAtIndex(0).isModified = -> true
|
||||
spyOn(atom, "confirm").andCallFake (a, b, c, d, e, f, g, noSaveFn) -> noSaveFn()
|
||||
|
||||
promiseHandler = jasmine.createSpy("promiseHandler")
|
||||
failedPromiseHandler = jasmine.createSpy("failedPromiseHandler")
|
||||
promise = container.confirmClose()
|
||||
promise.done promiseHandler
|
||||
promise.fail failedPromiseHandler
|
||||
|
||||
waitsFor ->
|
||||
promiseHandler.wasCalled
|
||||
|
||||
runs ->
|
||||
expect(failedPromiseHandler).not.toHaveBeenCalled()
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
|
||||
it "rejects the returned promise if the user cancels saving", ->
|
||||
pane1.itemAtIndex(0).isModified = -> true
|
||||
pane2.itemAtIndex(0).isModified = -> true
|
||||
spyOn(atom, "confirm").andCallFake (a, b, c, d, e, cancelFn, f, g) -> cancelFn()
|
||||
|
||||
promiseHandler = jasmine.createSpy("promiseHandler")
|
||||
failedPromiseHandler = jasmine.createSpy("failedPromiseHandler")
|
||||
promise = container.confirmClose()
|
||||
promise.done promiseHandler
|
||||
promise.fail failedPromiseHandler
|
||||
|
||||
waitsFor ->
|
||||
failedPromiseHandler.wasCalled
|
||||
|
||||
runs ->
|
||||
expect(promiseHandler).not.toHaveBeenCalled()
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
|
||||
describe "serialization", ->
|
||||
it "can be serialized and deserialized, and correctly adjusts dimensions of deserialized panes after attach", ->
|
||||
newContainer = deserialize(container.serialize())
|
||||
expect(newContainer.find('.row > :contains(1)')).toExist()
|
||||
expect(newContainer.find('.row > .column > :contains(2)')).toExist()
|
||||
expect(newContainer.find('.row > .column > :contains(3)')).toExist()
|
||||
|
||||
newContainer.height(200).width(300).attachToDom()
|
||||
expect(newContainer.find('.row > :contains(1)').width()).toBe 150
|
||||
expect(newContainer.find('.row > .column > :contains(2)').height()).toBe 100
|
||||
|
||||
it "removes empty panes on deserialization", ->
|
||||
# only deserialize pane 1's view successfully
|
||||
TestView.deserialize = ({name}) -> new TestView(name) if name is '1'
|
||||
newContainer = deserialize(container.serialize())
|
||||
expect(newContainer.find('.row, .column')).not.toExist()
|
||||
expect(newContainer.find('> :contains(1)')).toExist()
|
||||
701
spec/app/pane-spec.coffee
Normal file
701
spec/app/pane-spec.coffee
Normal file
@@ -0,0 +1,701 @@
|
||||
PaneContainer = require 'pane-container'
|
||||
Pane = require 'pane'
|
||||
{$$} = require 'space-pen'
|
||||
$ = require 'jquery'
|
||||
|
||||
describe "Pane", ->
|
||||
[container, view1, view2, editSession1, editSession2, pane] = []
|
||||
|
||||
beforeEach ->
|
||||
container = new PaneContainer
|
||||
view1 = $$ -> @div id: 'view-1', tabindex: -1, 'View 1'
|
||||
view2 = $$ -> @div id: 'view-2', tabindex: -1, 'View 2'
|
||||
editSession1 = project.buildEditSession('sample.js')
|
||||
editSession2 = project.buildEditSession('sample.txt')
|
||||
pane = new Pane(view1, editSession1, view2, editSession2)
|
||||
container.append(pane)
|
||||
|
||||
describe ".initialize(items...)", ->
|
||||
it "displays the first item in the pane", ->
|
||||
expect(pane.itemViews.find('#view-1')).toExist()
|
||||
|
||||
describe ".showItem(item)", ->
|
||||
it "hides all item views except the one being shown and sets the activeItem", ->
|
||||
expect(pane.activeItem).toBe view1
|
||||
pane.showItem(view2)
|
||||
expect(view1.css('display')).toBe 'none'
|
||||
expect(view2.css('display')).toBe ''
|
||||
expect(pane.activeItem).toBe view2
|
||||
|
||||
it "triggers 'pane:active-item-changed' if the item isn't already the activeItem", ->
|
||||
pane.makeActive()
|
||||
itemChangedHandler = jasmine.createSpy("itemChangedHandler")
|
||||
container.on 'pane:active-item-changed', itemChangedHandler
|
||||
|
||||
expect(pane.activeItem).toBe view1
|
||||
pane.showItem(view2)
|
||||
pane.showItem(view2)
|
||||
expect(itemChangedHandler.callCount).toBe 1
|
||||
expect(itemChangedHandler.argsForCall[0][1]).toBe view2
|
||||
itemChangedHandler.reset()
|
||||
|
||||
pane.showItem(editSession1)
|
||||
expect(itemChangedHandler).toHaveBeenCalled()
|
||||
expect(itemChangedHandler.argsForCall[0][1]).toBe editSession1
|
||||
itemChangedHandler.reset()
|
||||
|
||||
describe "if the pane's active view is focused before calling showItem", ->
|
||||
it "focuses the new active view", ->
|
||||
container.attachToDom()
|
||||
pane.focus()
|
||||
expect(pane.activeView).not.toBe view2
|
||||
expect(pane.activeView).toMatchSelector ':focus'
|
||||
pane.showItem(view2)
|
||||
expect(view2).toMatchSelector ':focus'
|
||||
|
||||
describe "when the given item isn't yet in the items list on the pane", ->
|
||||
view3 = null
|
||||
beforeEach ->
|
||||
view3 = $$ -> @div id: 'view-3', "View 3"
|
||||
pane.showItem(editSession1)
|
||||
expect(pane.getActiveItemIndex()).toBe 1
|
||||
|
||||
it "adds it to the items list after the active item", ->
|
||||
pane.showItem(view3)
|
||||
expect(pane.getItems()).toEqual [view1, editSession1, view3, view2, editSession2]
|
||||
expect(pane.activeItem).toBe view3
|
||||
expect(pane.getActiveItemIndex()).toBe 2
|
||||
|
||||
it "triggers the 'item-added' event with the item and its index before the 'active-item-changed' event", ->
|
||||
events = []
|
||||
container.on 'pane:item-added', (e, item, index) -> events.push(['pane:item-added', item, index])
|
||||
container.on 'pane:active-item-changed', (e, item) -> events.push(['pane:active-item-changed', item])
|
||||
pane.showItem(view3)
|
||||
expect(events).toEqual [['pane:item-added', view3, 2], ['pane:active-item-changed', view3]]
|
||||
|
||||
describe "when showing a model item", ->
|
||||
describe "when no view has yet been appended for that item", ->
|
||||
it "appends and shows a view to display the item based on its `.getViewClass` method", ->
|
||||
pane.showItem(editSession1)
|
||||
editor = pane.activeView
|
||||
expect(editor.css('display')).toBe ''
|
||||
expect(editor.activeEditSession).toBe editSession1
|
||||
|
||||
describe "when a valid view has already been appended for another item", ->
|
||||
it "recycles the existing view by assigning the selected item to it", ->
|
||||
pane.showItem(editSession1)
|
||||
pane.showItem(editSession2)
|
||||
expect(pane.itemViews.find('.editor').length).toBe 1
|
||||
editor = pane.activeView
|
||||
expect(editor.css('display')).toBe ''
|
||||
expect(editor.activeEditSession).toBe editSession2
|
||||
|
||||
describe "when showing a view item", ->
|
||||
it "appends it to the itemViews div if it hasn't already been appended and shows it", ->
|
||||
expect(pane.itemViews.find('#view-2')).not.toExist()
|
||||
pane.showItem(view2)
|
||||
expect(pane.itemViews.find('#view-2')).toExist()
|
||||
expect(pane.activeView).toBe view2
|
||||
|
||||
describe ".destroyItem(item)", ->
|
||||
describe "if the item is not modified", ->
|
||||
it "removes the item and tries to call destroy on it", ->
|
||||
pane.destroyItem(editSession2)
|
||||
expect(pane.getItems().indexOf(editSession2)).toBe -1
|
||||
expect(editSession2.destroyed).toBeTruthy()
|
||||
|
||||
describe "if the item is modified", ->
|
||||
beforeEach ->
|
||||
spyOn(atom, 'confirm')
|
||||
spyOn(atom, 'showSaveDialog')
|
||||
spyOn(editSession2, 'save')
|
||||
spyOn(editSession2, 'saveAs')
|
||||
|
||||
atom.confirm.selectOption = (buttonText) ->
|
||||
for arg, i in @argsForCall[0] when arg is buttonText
|
||||
@argsForCall[0][i + 1]?()
|
||||
|
||||
editSession2.insertText('a')
|
||||
expect(editSession2.isModified()).toBeTruthy()
|
||||
pane.destroyItem(editSession2)
|
||||
|
||||
it "presents a dialog with the option to save the item first", ->
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
expect(pane.getItems().indexOf(editSession2)).not.toBe -1
|
||||
expect(editSession2.destroyed).toBeFalsy()
|
||||
|
||||
describe "if the [Save] option is selected", ->
|
||||
describe "when the item has a uri", ->
|
||||
it "saves the item before removing and destroying it", ->
|
||||
atom.confirm.selectOption('Save')
|
||||
|
||||
expect(editSession2.save).toHaveBeenCalled()
|
||||
expect(pane.getItems().indexOf(editSession2)).toBe -1
|
||||
expect(editSession2.destroyed).toBeTruthy()
|
||||
|
||||
describe "when the item has no uri", ->
|
||||
it "presents a save-as dialog, then saves the item with the given uri before removing and destroying it", ->
|
||||
editSession2.buffer.setPath(undefined)
|
||||
|
||||
atom.confirm.selectOption('Save')
|
||||
|
||||
expect(atom.showSaveDialog).toHaveBeenCalled()
|
||||
|
||||
atom.showSaveDialog.argsForCall[0][0]("/selected/path")
|
||||
|
||||
expect(editSession2.saveAs).toHaveBeenCalledWith("/selected/path")
|
||||
expect(pane.getItems().indexOf(editSession2)).toBe -1
|
||||
expect(editSession2.destroyed).toBeTruthy()
|
||||
|
||||
describe "if the [Don't Save] option is selected", ->
|
||||
it "removes and destroys the item without saving it", ->
|
||||
atom.confirm.selectOption("Don't Save")
|
||||
|
||||
expect(editSession2.save).not.toHaveBeenCalled()
|
||||
expect(pane.getItems().indexOf(editSession2)).toBe -1
|
||||
expect(editSession2.destroyed).toBeTruthy()
|
||||
|
||||
describe "if the [Cancel] option is selected", ->
|
||||
it "does not save, remove, or destroy the item", ->
|
||||
atom.confirm.selectOption("Cancel")
|
||||
|
||||
expect(editSession2.save).not.toHaveBeenCalled()
|
||||
expect(pane.getItems().indexOf(editSession2)).not.toBe -1
|
||||
expect(editSession2.destroyed).toBeFalsy()
|
||||
|
||||
describe ".removeItem(item)", ->
|
||||
it "removes the item from the items list and shows the next item if it was showing", ->
|
||||
pane.removeItem(view1)
|
||||
expect(pane.getItems()).toEqual [editSession1, view2, editSession2]
|
||||
expect(pane.activeItem).toBe editSession1
|
||||
|
||||
pane.showItem(editSession2)
|
||||
pane.removeItem(editSession2)
|
||||
expect(pane.getItems()).toEqual [editSession1, view2]
|
||||
expect(pane.activeItem).toBe editSession1
|
||||
|
||||
it "triggers 'pane:item-removed' with the item and its former index", ->
|
||||
itemRemovedHandler = jasmine.createSpy("itemRemovedHandler")
|
||||
pane.on 'pane:item-removed', itemRemovedHandler
|
||||
pane.removeItem(editSession1)
|
||||
expect(itemRemovedHandler).toHaveBeenCalled()
|
||||
expect(itemRemovedHandler.argsForCall[0][1..2]).toEqual [editSession1, 1]
|
||||
|
||||
describe "when removing the last item", ->
|
||||
it "removes the pane", ->
|
||||
pane.removeItem(item) for item in pane.getItems()
|
||||
expect(pane.hasParent()).toBeFalsy()
|
||||
|
||||
describe "when the pane is focused", ->
|
||||
it "shifts focus to the next pane", ->
|
||||
container.attachToDom()
|
||||
pane2 = pane.splitRight($$ -> @div class: 'view-3', tabindex: -1, 'View 3')
|
||||
pane.focus()
|
||||
expect(pane).toMatchSelector(':has(:focus)')
|
||||
pane.removeItem(item) for item in pane.getItems()
|
||||
expect(pane2).toMatchSelector ':has(:focus)'
|
||||
|
||||
describe "when the item is a view", ->
|
||||
it "removes the item from the 'item-views' div", ->
|
||||
expect(view1.parent()).toMatchSelector pane.itemViews
|
||||
pane.removeItem(view1)
|
||||
expect(view1.parent()).not.toMatchSelector pane.itemViews
|
||||
|
||||
describe "when the item is a model", ->
|
||||
it "removes the associated view only when all items that require it have been removed", ->
|
||||
pane.showItem(editSession2)
|
||||
pane.removeItem(editSession2)
|
||||
expect(pane.itemViews.find('.editor')).toExist()
|
||||
pane.removeItem(editSession1)
|
||||
expect(pane.itemViews.find('.editor')).not.toExist()
|
||||
|
||||
describe ".moveItem(item, index)", ->
|
||||
it "moves the item to the given index and emits a 'pane:item-moved' event with the item and the new index", ->
|
||||
itemMovedHandler = jasmine.createSpy("itemMovedHandler")
|
||||
pane.on 'pane:item-moved', itemMovedHandler
|
||||
|
||||
pane.moveItem(view1, 2)
|
||||
expect(pane.getItems()).toEqual [editSession1, view2, view1, editSession2]
|
||||
expect(itemMovedHandler).toHaveBeenCalled()
|
||||
expect(itemMovedHandler.argsForCall[0][1..2]).toEqual [view1, 2]
|
||||
itemMovedHandler.reset()
|
||||
|
||||
pane.moveItem(editSession1, 3)
|
||||
expect(pane.getItems()).toEqual [view2, view1, editSession2, editSession1]
|
||||
expect(itemMovedHandler).toHaveBeenCalled()
|
||||
expect(itemMovedHandler.argsForCall[0][1..2]).toEqual [editSession1, 3]
|
||||
itemMovedHandler.reset()
|
||||
|
||||
pane.moveItem(editSession1, 1)
|
||||
expect(pane.getItems()).toEqual [view2, editSession1, view1, editSession2]
|
||||
expect(itemMovedHandler).toHaveBeenCalled()
|
||||
expect(itemMovedHandler.argsForCall[0][1..2]).toEqual [editSession1, 1]
|
||||
itemMovedHandler.reset()
|
||||
|
||||
describe ".moveItemToPane(item, pane, index)", ->
|
||||
[pane2, view3] = []
|
||||
|
||||
beforeEach ->
|
||||
view3 = $$ -> @div id: 'view-3', "View 3"
|
||||
pane2 = pane.splitRight(view3)
|
||||
|
||||
it "moves the item to the given pane at the given index", ->
|
||||
pane.moveItemToPane(view1, pane2, 1)
|
||||
expect(pane.getItems()).toEqual [editSession1, view2, editSession2]
|
||||
expect(pane2.getItems()).toEqual [view3, view1]
|
||||
|
||||
describe "when it is the last item on the source pane", ->
|
||||
it "removes the source pane, but does not destroy the item", ->
|
||||
pane.removeItem(view1)
|
||||
pane.removeItem(view2)
|
||||
pane.removeItem(editSession2)
|
||||
|
||||
expect(pane.getItems()).toEqual [editSession1]
|
||||
pane.moveItemToPane(editSession1, pane2, 1)
|
||||
|
||||
expect(pane.hasParent()).toBeFalsy()
|
||||
expect(pane2.getItems()).toEqual [view3, editSession1]
|
||||
expect(editSession1.destroyed).toBeFalsy()
|
||||
|
||||
describe "core:close", ->
|
||||
it "destroys the active item and does not bubble the event", ->
|
||||
containerCloseHandler = jasmine.createSpy("containerCloseHandler")
|
||||
container.on 'core:close', containerCloseHandler
|
||||
|
||||
pane.showItem(editSession1)
|
||||
initialItemCount = pane.getItems().length
|
||||
pane.trigger 'core:close'
|
||||
expect(pane.getItems().length).toBe initialItemCount - 1
|
||||
expect(editSession1.destroyed).toBeTruthy()
|
||||
|
||||
expect(containerCloseHandler).not.toHaveBeenCalled()
|
||||
|
||||
describe "pane:close", ->
|
||||
it "destroys all items and removes the pane", ->
|
||||
pane.showItem(editSession1)
|
||||
pane.trigger 'pane:close'
|
||||
expect(pane.hasParent()).toBeFalsy()
|
||||
expect(editSession2.destroyed).toBeTruthy()
|
||||
expect(editSession1.destroyed).toBeTruthy()
|
||||
|
||||
describe "pane:close-other-items", ->
|
||||
it "destroys all items except the current", ->
|
||||
pane.showItem(editSession1)
|
||||
pane.trigger 'pane:close-other-items'
|
||||
expect(editSession2.destroyed).toBeTruthy()
|
||||
expect(pane.getItems()).toEqual [editSession1]
|
||||
|
||||
describe "core:save", ->
|
||||
describe "when the current item has a uri", ->
|
||||
describe "when the current item has a save method", ->
|
||||
it "saves the current item", ->
|
||||
spyOn(editSession2, 'save')
|
||||
pane.showItem(editSession2)
|
||||
pane.trigger 'core:save'
|
||||
expect(editSession2.save).toHaveBeenCalled()
|
||||
|
||||
describe "when the current item has no save method", ->
|
||||
it "does nothing", ->
|
||||
expect(pane.activeItem.save).toBeUndefined()
|
||||
pane.trigger 'core:save'
|
||||
|
||||
describe "when the current item has no uri", ->
|
||||
beforeEach ->
|
||||
spyOn(atom, 'showSaveDialog')
|
||||
|
||||
describe "when the current item has a saveAs method", ->
|
||||
it "opens a save dialog and saves the current item as the selected path", ->
|
||||
spyOn(editSession2, 'saveAs')
|
||||
editSession2.buffer.setPath(undefined)
|
||||
pane.showItem(editSession2)
|
||||
|
||||
pane.trigger 'core:save'
|
||||
|
||||
expect(atom.showSaveDialog).toHaveBeenCalled()
|
||||
atom.showSaveDialog.argsForCall[0][0]('/selected/path')
|
||||
expect(editSession2.saveAs).toHaveBeenCalledWith('/selected/path')
|
||||
|
||||
describe "when the current item has no saveAs method", ->
|
||||
it "does nothing", ->
|
||||
expect(pane.activeItem.saveAs).toBeUndefined()
|
||||
pane.trigger 'core:save'
|
||||
expect(atom.showSaveDialog).not.toHaveBeenCalled()
|
||||
|
||||
describe "core:save-as", ->
|
||||
beforeEach ->
|
||||
spyOn(atom, 'showSaveDialog')
|
||||
|
||||
describe "when the current item has a saveAs method", ->
|
||||
it "opens the save dialog and calls saveAs on the item with the selected path", ->
|
||||
spyOn(editSession2, 'saveAs')
|
||||
pane.showItem(editSession2)
|
||||
|
||||
pane.trigger 'core:save-as'
|
||||
|
||||
expect(atom.showSaveDialog).toHaveBeenCalled()
|
||||
atom.showSaveDialog.argsForCall[0][0]('/selected/path')
|
||||
expect(editSession2.saveAs).toHaveBeenCalledWith('/selected/path')
|
||||
|
||||
describe "when the current item does not have a saveAs method", ->
|
||||
it "does nothing", ->
|
||||
expect(pane.activeItem.saveAs).toBeUndefined()
|
||||
pane.trigger 'core:save-as'
|
||||
expect(atom.showSaveDialog).not.toHaveBeenCalled()
|
||||
|
||||
describe "pane:show-next-item and pane:show-previous-item", ->
|
||||
it "advances forward/backward through the pane's items, looping around at either end", ->
|
||||
expect(pane.activeItem).toBe view1
|
||||
pane.trigger 'pane:show-previous-item'
|
||||
expect(pane.activeItem).toBe editSession2
|
||||
pane.trigger 'pane:show-previous-item'
|
||||
expect(pane.activeItem).toBe view2
|
||||
pane.trigger 'pane:show-next-item'
|
||||
expect(pane.activeItem).toBe editSession2
|
||||
pane.trigger 'pane:show-next-item'
|
||||
expect(pane.activeItem).toBe view1
|
||||
|
||||
describe "pane:show-item-N events", ->
|
||||
it "shows the (n-1)th item if it exists", ->
|
||||
pane.trigger 'pane:show-item-2'
|
||||
expect(pane.activeItem).toBe pane.itemAtIndex(1)
|
||||
pane.trigger 'pane:show-item-1'
|
||||
expect(pane.activeItem).toBe pane.itemAtIndex(0)
|
||||
pane.trigger 'pane:show-item-9' # don't fail on out-of-bounds indices
|
||||
expect(pane.activeItem).toBe pane.itemAtIndex(0)
|
||||
|
||||
describe "when the title of the active item changes", ->
|
||||
it "emits pane:active-item-title-changed", ->
|
||||
activeItemTitleChangedHandler = jasmine.createSpy("activeItemTitleChangedHandler")
|
||||
pane.on 'pane:active-item-title-changed', activeItemTitleChangedHandler
|
||||
|
||||
expect(pane.activeItem).toBe view1
|
||||
|
||||
view2.trigger 'title-changed'
|
||||
expect(activeItemTitleChangedHandler).not.toHaveBeenCalled()
|
||||
|
||||
view1.trigger 'title-changed'
|
||||
expect(activeItemTitleChangedHandler).toHaveBeenCalled()
|
||||
activeItemTitleChangedHandler.reset()
|
||||
|
||||
pane.showItem(view2)
|
||||
view2.trigger 'title-changed'
|
||||
expect(activeItemTitleChangedHandler).toHaveBeenCalled()
|
||||
|
||||
describe ".remove()", ->
|
||||
it "destroys all the pane's items", ->
|
||||
pane.remove()
|
||||
expect(editSession1.destroyed).toBeTruthy()
|
||||
expect(editSession2.destroyed).toBeTruthy()
|
||||
|
||||
it "triggers a 'pane:removed' event with the pane", ->
|
||||
removedHandler = jasmine.createSpy("removedHandler")
|
||||
container.on 'pane:removed', removedHandler
|
||||
pane.remove()
|
||||
expect(removedHandler).toHaveBeenCalled()
|
||||
expect(removedHandler.argsForCall[0][1]).toBe pane
|
||||
|
||||
describe "when there are other panes", ->
|
||||
[paneToLeft, paneToRight] = []
|
||||
|
||||
beforeEach ->
|
||||
pane.showItem(editSession1)
|
||||
paneToLeft = pane.splitLeft()
|
||||
paneToRight = pane.splitRight()
|
||||
container.attachToDom()
|
||||
|
||||
describe "when the removed pane is focused", ->
|
||||
it "activates and focuses the next pane", ->
|
||||
pane.focus()
|
||||
pane.remove()
|
||||
expect(paneToLeft.isActive()).toBeFalsy()
|
||||
expect(paneToRight.isActive()).toBeTruthy()
|
||||
expect(paneToRight).toMatchSelector ':has(:focus)'
|
||||
|
||||
describe "when the removed pane is active but not focused", ->
|
||||
it "activates the next pane, but does not focus it", ->
|
||||
$(document.activeElement).blur()
|
||||
expect(pane).not.toMatchSelector ':has(:focus)'
|
||||
pane.makeActive()
|
||||
pane.remove()
|
||||
expect(paneToLeft.isActive()).toBeFalsy()
|
||||
expect(paneToRight.isActive()).toBeTruthy()
|
||||
expect(paneToRight).not.toMatchSelector ':has(:focus)'
|
||||
|
||||
describe "when the removed pane is not active", ->
|
||||
it "does not affect the active pane or the focus", ->
|
||||
paneToLeft.focus()
|
||||
expect(paneToLeft.isActive()).toBeTruthy()
|
||||
expect(paneToRight.isActive()).toBeFalsy()
|
||||
|
||||
pane.remove()
|
||||
expect(paneToLeft.isActive()).toBeTruthy()
|
||||
expect(paneToRight.isActive()).toBeFalsy()
|
||||
expect(paneToLeft).toMatchSelector ':has(:focus)'
|
||||
|
||||
describe "when it is the last pane", ->
|
||||
beforeEach ->
|
||||
expect(container.getPanes().length).toBe 1
|
||||
window.rootView = focus: jasmine.createSpy("rootView.focus")
|
||||
|
||||
describe "when the removed pane is focused", ->
|
||||
it "calls focus on rootView so we don't lose focus", ->
|
||||
container.attachToDom()
|
||||
pane.focus()
|
||||
pane.remove()
|
||||
expect(rootView.focus).toHaveBeenCalled()
|
||||
|
||||
describe "when the removed pane is not focused", ->
|
||||
it "does not call focus on root view", ->
|
||||
expect(pane).not.toMatchSelector ':has(:focus)'
|
||||
pane.remove()
|
||||
expect(rootView.focus).not.toHaveBeenCalled()
|
||||
|
||||
describe ".getNextPane()", ->
|
||||
it "returns the next pane if one exists, wrapping around from the last pane to the first", ->
|
||||
pane.showItem(editSession1)
|
||||
expect(pane.getNextPane()).toBeUndefined
|
||||
pane2 = pane.splitRight()
|
||||
expect(pane.getNextPane()).toBe pane2
|
||||
expect(pane2.getNextPane()).toBe pane
|
||||
|
||||
describe "when the pane is focused", ->
|
||||
it "focuses the active item view", ->
|
||||
focusHandler = jasmine.createSpy("focusHandler")
|
||||
pane.activeItem.on 'focus', focusHandler
|
||||
pane.focus()
|
||||
expect(focusHandler).toHaveBeenCalled()
|
||||
|
||||
it "triggers 'pane:became-active' if it was not previously active", ->
|
||||
becameActiveHandler = jasmine.createSpy("becameActiveHandler")
|
||||
container.on 'pane:became-active', becameActiveHandler
|
||||
|
||||
expect(pane.isActive()).toBeFalsy()
|
||||
pane.focusin()
|
||||
expect(pane.isActive()).toBeTruthy()
|
||||
pane.focusin()
|
||||
|
||||
expect(becameActiveHandler.callCount).toBe 1
|
||||
|
||||
describe "split methods", ->
|
||||
[pane1, view3, view4] = []
|
||||
beforeEach ->
|
||||
pane1 = pane
|
||||
pane.showItem(editSession1)
|
||||
view3 = $$ -> @div id: 'view-3', 'View 3'
|
||||
view4 = $$ -> @div id: 'view-4', 'View 4'
|
||||
|
||||
describe "splitRight(items...)", ->
|
||||
it "builds a row if needed, then appends a new pane after itself", ->
|
||||
# creates the new pane with a copy of the active item if none are given
|
||||
pane2 = pane1.splitRight()
|
||||
expect(container.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]]
|
||||
expect(pane2.items).toEqual [editSession1]
|
||||
expect(pane2.activeItem).not.toBe editSession1 # it's a copy
|
||||
|
||||
pane3 = pane2.splitRight(view3, view4)
|
||||
expect(pane3.getItems()).toEqual [view3, view4]
|
||||
expect(container.find('.row .pane').toArray()).toEqual [pane[0], pane2[0], pane3[0]]
|
||||
|
||||
describe "splitRight(items...)", ->
|
||||
it "builds a row if needed, then appends a new pane before itself", ->
|
||||
# creates the new pane with a copy of the active item if none are given
|
||||
pane2 = pane.splitLeft()
|
||||
expect(container.find('.row .pane').toArray()).toEqual [pane2[0], pane[0]]
|
||||
expect(pane2.items).toEqual [editSession1]
|
||||
expect(pane2.activeItem).not.toBe editSession1 # it's a copy
|
||||
|
||||
pane3 = pane2.splitLeft(view3, view4)
|
||||
expect(pane3.getItems()).toEqual [view3, view4]
|
||||
expect(container.find('.row .pane').toArray()).toEqual [pane3[0], pane2[0], pane[0]]
|
||||
|
||||
describe "splitDown(items...)", ->
|
||||
it "builds a column if needed, then appends a new pane after itself", ->
|
||||
# creates the new pane with a copy of the active item if none are given
|
||||
pane2 = pane.splitDown()
|
||||
expect(container.find('.column .pane').toArray()).toEqual [pane[0], pane2[0]]
|
||||
expect(pane2.items).toEqual [editSession1]
|
||||
expect(pane2.activeItem).not.toBe editSession1 # it's a copy
|
||||
|
||||
pane3 = pane2.splitDown(view3, view4)
|
||||
expect(pane3.getItems()).toEqual [view3, view4]
|
||||
expect(container.find('.column .pane').toArray()).toEqual [pane[0], pane2[0], pane3[0]]
|
||||
|
||||
describe "splitUp(items...)", ->
|
||||
it "builds a column if needed, then appends a new pane before itself", ->
|
||||
# creates the new pane with a copy of the active item if none are given
|
||||
pane2 = pane.splitUp()
|
||||
expect(container.find('.column .pane').toArray()).toEqual [pane2[0], pane[0]]
|
||||
expect(pane2.items).toEqual [editSession1]
|
||||
expect(pane2.activeItem).not.toBe editSession1 # it's a copy
|
||||
|
||||
pane3 = pane2.splitUp(view3, view4)
|
||||
expect(pane3.getItems()).toEqual [view3, view4]
|
||||
expect(container.find('.column .pane').toArray()).toEqual [pane3[0], pane2[0], pane[0]]
|
||||
|
||||
it "lays out nested panes by equally dividing their containing row / column", ->
|
||||
container.width(520).height(240).attachToDom()
|
||||
pane1.showItem($("1"))
|
||||
pane1
|
||||
.splitLeft($("2"))
|
||||
.splitUp($("3"))
|
||||
.splitLeft($("4"))
|
||||
.splitDown($("5"))
|
||||
|
||||
row1 = container.children(':eq(0)')
|
||||
expect(row1.children().length).toBe 2
|
||||
column1 = row1.children(':eq(0)').view()
|
||||
pane1 = row1.children(':eq(1)').view()
|
||||
expect(column1.outerWidth()).toBe Math.round(2/3 * container.width())
|
||||
expect(column1.outerHeight()).toBe container.height()
|
||||
expect(pane1.outerWidth()).toBe Math.round(1/3 * container.width())
|
||||
expect(pane1.outerHeight()).toBe container.height()
|
||||
expect(Math.round(pane1.position().left)).toBe column1.outerWidth()
|
||||
|
||||
expect(column1.children().length).toBe 2
|
||||
row2 = column1.children(':eq(0)').view()
|
||||
pane2 = column1.children(':eq(1)').view()
|
||||
expect(row2.outerWidth()).toBe column1.outerWidth()
|
||||
expect(row2.height()).toBe 2/3 * container.height()
|
||||
expect(pane2.outerWidth()).toBe column1.outerWidth()
|
||||
expect(pane2.outerHeight()).toBe 1/3 * container.height()
|
||||
expect(pane2.position().top).toBe row2.height()
|
||||
|
||||
expect(row2.children().length).toBe 2
|
||||
column3 = row2.children(':eq(0)').view()
|
||||
pane3 = row2.children(':eq(1)').view()
|
||||
expect(column3.outerWidth()).toBe Math.round(1/3 * container.width())
|
||||
expect(column3.outerHeight()).toBe row2.outerHeight()
|
||||
# the built in rounding seems to be rounding x.5 down, but we need to go up. this sucks.
|
||||
expect(Math.round(pane3.trueWidth())).toBe Math.round(1/3 * container.width())
|
||||
expect(pane3.height()).toBe row2.outerHeight()
|
||||
expect(Math.round(pane3.position().left)).toBe column3.width()
|
||||
|
||||
expect(column3.children().length).toBe 2
|
||||
pane4 = column3.children(':eq(0)').view()
|
||||
pane5 = column3.children(':eq(1)').view()
|
||||
expect(pane4.outerWidth()).toBe column3.width()
|
||||
expect(pane4.outerHeight()).toBe 1/3 * container.height()
|
||||
expect(pane5.outerWidth()).toBe column3.width()
|
||||
expect(pane5.position().top).toBe pane4.outerHeight()
|
||||
expect(pane5.outerHeight()).toBe 1/3 * container.height()
|
||||
|
||||
pane5.remove()
|
||||
expect(column3.parent()).not.toExist()
|
||||
expect(pane2.outerHeight()).toBe Math.floor(1/2 * container.height())
|
||||
expect(pane3.outerHeight()).toBe Math.floor(1/2 * container.height())
|
||||
expect(pane4.outerHeight()).toBe Math.floor(1/2 * container.height())
|
||||
|
||||
pane4.remove()
|
||||
expect(row2.parent()).not.toExist()
|
||||
expect(pane1.outerWidth()).toBe Math.floor(1/2 * container.width())
|
||||
expect(pane2.outerWidth()).toBe Math.floor(1/2 * container.width())
|
||||
expect(pane3.outerWidth()).toBe Math.floor(1/2 * container.width())
|
||||
|
||||
pane3.remove()
|
||||
expect(column1.parent()).not.toExist()
|
||||
expect(pane2.outerHeight()).toBe container.height()
|
||||
|
||||
pane2.remove()
|
||||
expect(row1.parent()).not.toExist()
|
||||
expect(container.children().length).toBe 1
|
||||
expect(container.children('.pane').length).toBe 1
|
||||
expect(pane1.outerWidth()).toBe container.width()
|
||||
|
||||
describe "autosave", ->
|
||||
[initialActiveItem, initialActiveItemUri] = []
|
||||
|
||||
beforeEach ->
|
||||
initialActiveItem = pane.activeItem
|
||||
initialActiveItemUri = null
|
||||
pane.activeItem.getUri = -> initialActiveItemUri
|
||||
pane.activeItem.save = jasmine.createSpy("activeItem.save")
|
||||
spyOn(pane, 'saveItem').andCallThrough()
|
||||
|
||||
describe "when the active view loses focus", ->
|
||||
it "saves the item if core.autosave is true and the item has a uri", ->
|
||||
pane.activeView.trigger 'focusout'
|
||||
expect(pane.saveItem).not.toHaveBeenCalled()
|
||||
expect(pane.activeItem.save).not.toHaveBeenCalled()
|
||||
|
||||
config.set('core.autosave', true)
|
||||
pane.activeView.trigger 'focusout'
|
||||
expect(pane.saveItem).not.toHaveBeenCalled()
|
||||
expect(pane.activeItem.save).not.toHaveBeenCalled()
|
||||
|
||||
initialActiveItemUri = '/tmp/hi'
|
||||
pane.activeView.trigger 'focusout'
|
||||
expect(pane.activeItem.save).toHaveBeenCalled()
|
||||
|
||||
describe "when an item becomes inactive", ->
|
||||
it "saves the item if core.autosave is true and the item has a uri", ->
|
||||
expect(view2).not.toBe pane.activeItem
|
||||
expect(pane.saveItem).not.toHaveBeenCalled()
|
||||
expect(initialActiveItem.save).not.toHaveBeenCalled()
|
||||
pane.showItem(view2)
|
||||
|
||||
pane.showItem(initialActiveItem)
|
||||
config.set('core.autosave', true)
|
||||
pane.showItem(view2)
|
||||
expect(pane.saveItem).not.toHaveBeenCalled()
|
||||
expect(initialActiveItem.save).not.toHaveBeenCalled()
|
||||
|
||||
pane.showItem(initialActiveItem)
|
||||
initialActiveItemUri = '/tmp/hi'
|
||||
pane.showItem(view2)
|
||||
expect(initialActiveItem.save).toHaveBeenCalled()
|
||||
|
||||
describe "when an item is destroyed", ->
|
||||
it "saves the item if core.autosave is true and the item has a uri", ->
|
||||
# doesn't have to be the active item
|
||||
expect(view2).not.toBe pane.activeItem
|
||||
pane.showItem(view2)
|
||||
|
||||
pane.destroyItem(editSession1)
|
||||
expect(pane.saveItem).not.toHaveBeenCalled()
|
||||
|
||||
config.set("core.autosave", true)
|
||||
view2.getUri = -> undefined
|
||||
view2.save = ->
|
||||
pane.destroyItem(view2)
|
||||
expect(pane.saveItem).not.toHaveBeenCalled()
|
||||
|
||||
initialActiveItemUri = '/tmp/hi'
|
||||
pane.destroyItem(initialActiveItem)
|
||||
expect(initialActiveItem.save).toHaveBeenCalled()
|
||||
|
||||
describe ".itemForUri(uri)", ->
|
||||
it "returns the item for which a call to .getUri() returns the given uri", ->
|
||||
expect(pane.itemForUri(editSession1.getUri())).toBe editSession1
|
||||
expect(pane.itemForUri(editSession2.getUri())).toBe editSession2
|
||||
|
||||
describe "serialization", ->
|
||||
it "can serialize and deserialize the pane and all its serializable items", ->
|
||||
newPane = deserialize(pane.serialize())
|
||||
expect(newPane.getItems()).toEqual [editSession1, editSession2]
|
||||
|
||||
it "restores the active item on deserialization if it serializable", ->
|
||||
pane.showItem(editSession2)
|
||||
newPane = deserialize(pane.serialize())
|
||||
expect(newPane.activeItem).toEqual editSession2
|
||||
|
||||
it "defaults to the first item on deserialization if the active item was not serializable", ->
|
||||
expect(view2.serialize?()).toBeFalsy()
|
||||
pane.showItem(view2)
|
||||
newPane = deserialize(pane.serialize())
|
||||
expect(newPane.activeItem).toEqual editSession1
|
||||
|
||||
it "focuses the pane after attach only if had focus when serialized", ->
|
||||
container.attachToDom()
|
||||
|
||||
pane.focus()
|
||||
state = pane.serialize()
|
||||
pane.remove()
|
||||
newPane = deserialize(state)
|
||||
container.append(newPane)
|
||||
expect(newPane).toMatchSelector(':has(:focus)')
|
||||
|
||||
$(document.activeElement).blur()
|
||||
state = newPane.serialize()
|
||||
newPane.remove()
|
||||
newerPane = deserialize(state)
|
||||
expect(newerPane).not.toMatchSelector(':has(:focus)')
|
||||
@@ -3,17 +3,13 @@ fs = require 'fs'
|
||||
_ = require 'underscore'
|
||||
|
||||
describe "Project", ->
|
||||
project = null
|
||||
beforeEach ->
|
||||
project = new Project(require.resolve('fixtures/dir'))
|
||||
project.setPath(project.resolve('dir'))
|
||||
|
||||
afterEach ->
|
||||
project.destroy()
|
||||
|
||||
describe "when editSession is destroyed", ->
|
||||
describe "when an edit session is destroyed", ->
|
||||
it "removes edit session and calls destroy on buffer (if buffer is not referenced by other edit sessions)", ->
|
||||
editSession = project.buildEditSessionForPath("a")
|
||||
anotherEditSession = project.buildEditSessionForPath("a")
|
||||
editSession = project.buildEditSession("a")
|
||||
anotherEditSession = project.buildEditSession("a")
|
||||
|
||||
expect(project.editSessions.length).toBe 2
|
||||
expect(editSession.buffer).toBe anotherEditSession.buffer
|
||||
@@ -24,7 +20,17 @@ describe "Project", ->
|
||||
anotherEditSession.destroy()
|
||||
expect(project.editSessions.length).toBe 0
|
||||
|
||||
describe ".buildEditSessionForPath(path)", ->
|
||||
describe "when an edit session is saved and the project has no path", ->
|
||||
it "sets the project's path to the saved file's parent directory", ->
|
||||
path = project.resolve('a')
|
||||
project.setPath(undefined)
|
||||
expect(project.getPath()).toBeUndefined()
|
||||
editSession = project.buildEditSession()
|
||||
editSession.saveAs('/tmp/atom-test-save-sets-project-path')
|
||||
expect(project.getPath()).toBe '/tmp'
|
||||
fs.remove('/tmp/atom-test-save-sets-project-path')
|
||||
|
||||
describe ".buildEditSession(path)", ->
|
||||
[absolutePath, newBufferHandler, newEditSessionHandler] = []
|
||||
beforeEach ->
|
||||
absolutePath = require.resolve('fixtures/dir/a')
|
||||
@@ -35,30 +41,30 @@ describe "Project", ->
|
||||
|
||||
describe "when given an absolute path that hasn't been opened previously", ->
|
||||
it "returns a new edit session for the given path and emits 'buffer-created' and 'edit-session-created' events", ->
|
||||
editSession = project.buildEditSessionForPath(absolutePath)
|
||||
editSession = project.buildEditSession(absolutePath)
|
||||
expect(editSession.buffer.getPath()).toBe absolutePath
|
||||
expect(newBufferHandler).toHaveBeenCalledWith editSession.buffer
|
||||
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
|
||||
|
||||
describe "when given a relative path that hasn't been opened previously", ->
|
||||
it "returns a new edit session for the given path (relative to the project root) and emits 'buffer-created' and 'edit-session-created' events", ->
|
||||
editSession = project.buildEditSessionForPath('a')
|
||||
editSession = project.buildEditSession('a')
|
||||
expect(editSession.buffer.getPath()).toBe absolutePath
|
||||
expect(newBufferHandler).toHaveBeenCalledWith editSession.buffer
|
||||
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
|
||||
|
||||
describe "when passed the path to a buffer that has already been opened", ->
|
||||
it "returns a new edit session containing previously opened buffer and emits a 'edit-session-created' event", ->
|
||||
editSession = project.buildEditSessionForPath(absolutePath)
|
||||
editSession = project.buildEditSession(absolutePath)
|
||||
newBufferHandler.reset()
|
||||
expect(project.buildEditSessionForPath(absolutePath).buffer).toBe editSession.buffer
|
||||
expect(project.buildEditSessionForPath('a').buffer).toBe editSession.buffer
|
||||
expect(project.buildEditSession(absolutePath).buffer).toBe editSession.buffer
|
||||
expect(project.buildEditSession('a').buffer).toBe editSession.buffer
|
||||
expect(newBufferHandler).not.toHaveBeenCalled()
|
||||
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
|
||||
|
||||
describe "when not passed a path", ->
|
||||
it "returns a new edit session and emits 'buffer-created' and 'edit-session-created' events", ->
|
||||
editSession = project.buildEditSessionForPath()
|
||||
editSession = project.buildEditSession()
|
||||
expect(editSession.buffer.getPath()).toBeUndefined()
|
||||
expect(newBufferHandler).toHaveBeenCalledWith(editSession.buffer)
|
||||
expect(newEditSessionHandler).toHaveBeenCalledWith editSession
|
||||
|
||||
@@ -4,13 +4,13 @@ Project = require 'project'
|
||||
RootView = require 'root-view'
|
||||
Buffer = require 'buffer'
|
||||
Editor = require 'editor'
|
||||
Pane = require 'pane'
|
||||
{View, $$} = require 'space-pen'
|
||||
|
||||
describe "RootView", ->
|
||||
pathToOpen = null
|
||||
|
||||
beforeEach ->
|
||||
project.destroy()
|
||||
project.setPath(project.resolve('dir'))
|
||||
pathToOpen = project.resolve('a')
|
||||
window.rootView = new RootView
|
||||
@@ -23,37 +23,39 @@ describe "RootView", ->
|
||||
|
||||
describe "when the serialized RootView has an unsaved buffer", ->
|
||||
it "constructs the view with the same panes", ->
|
||||
rootView.attachToDom()
|
||||
rootView.open()
|
||||
editor1 = rootView.getActiveEditor()
|
||||
editor1 = rootView.getActiveView()
|
||||
buffer = editor1.getBuffer()
|
||||
editor1.splitRight()
|
||||
viewState = rootView.serialize()
|
||||
rootView.deactivate()
|
||||
|
||||
window.rootView = RootView.deserialize(viewState)
|
||||
rootView.focus()
|
||||
window.rootView = deserialize(viewState)
|
||||
rootView.attachToDom()
|
||||
|
||||
expect(rootView.getEditors().length).toBe 2
|
||||
expect(rootView.getActiveEditor().getText()).toBe buffer.getText()
|
||||
expect(rootView.getTitle()).toBe "untitled – #{project.getPath()}"
|
||||
expect(rootView.getActiveView().getText()).toBe buffer.getText()
|
||||
expect(rootView.title).toBe "untitled - #{project.getPath()}"
|
||||
|
||||
describe "when the serialized RootView has a project", ->
|
||||
describe "when there are open editors", ->
|
||||
it "constructs the view with the same panes", ->
|
||||
editor1 = rootView.getActiveEditor()
|
||||
editor2 = editor1.splitRight()
|
||||
editor3 = editor2.splitRight()
|
||||
editor4 = editor2.splitDown()
|
||||
editor2.edit(project.buildEditSessionForPath('b'))
|
||||
editor3.edit(project.buildEditSessionForPath('../sample.js'))
|
||||
editor3.setCursorScreenPosition([2, 4])
|
||||
editor4.edit(project.buildEditSessionForPath('../sample.txt'))
|
||||
editor4.setCursorScreenPosition([0, 2])
|
||||
rootView.attachToDom()
|
||||
editor2.focus()
|
||||
pane1 = rootView.getActivePane()
|
||||
pane2 = pane1.splitRight()
|
||||
pane3 = pane2.splitRight()
|
||||
pane4 = pane2.splitDown()
|
||||
pane2.showItem(project.buildEditSession('b'))
|
||||
pane3.showItem(project.buildEditSession('../sample.js'))
|
||||
pane3.activeItem.setCursorScreenPosition([2, 4])
|
||||
pane4.showItem(project.buildEditSession('../sample.txt'))
|
||||
pane4.activeItem.setCursorScreenPosition([0, 2])
|
||||
pane2.focus()
|
||||
|
||||
viewState = rootView.serialize()
|
||||
rootView.deactivate()
|
||||
window.rootView = RootView.deserialize(viewState)
|
||||
window.rootView = deserialize(viewState)
|
||||
rootView.attachToDom()
|
||||
|
||||
expect(rootView.getEditors().length).toBe 4
|
||||
@@ -81,276 +83,54 @@ describe "RootView", ->
|
||||
expect(editor3.isFocused).toBeFalsy()
|
||||
expect(editor4.isFocused).toBeFalsy()
|
||||
|
||||
expect(rootView.getTitle()).toBe "#{fs.base(editor2.getPath())} – #{project.getPath()}"
|
||||
expect(rootView.title).toBe "#{fs.base(editor2.getPath())} - #{project.getPath()}"
|
||||
|
||||
describe "where there are no open editors", ->
|
||||
it "constructs the view with no open editors", ->
|
||||
rootView.getActiveEditor().remove()
|
||||
rootView.getActivePane().remove()
|
||||
expect(rootView.getEditors().length).toBe 0
|
||||
|
||||
viewState = rootView.serialize()
|
||||
rootView.deactivate()
|
||||
window.rootView = RootView.deserialize(viewState)
|
||||
window.rootView = deserialize(viewState)
|
||||
|
||||
rootView.attachToDom()
|
||||
expect(rootView.getEditors().length).toBe 0
|
||||
|
||||
describe "when a pane's wrapped view cannot be deserialized", ->
|
||||
it "renders an empty pane", ->
|
||||
viewState =
|
||||
panesViewState:
|
||||
deserializer: "Pane",
|
||||
wrappedView:
|
||||
deserializer: "BogusView"
|
||||
|
||||
rootView.deactivate()
|
||||
window.rootView = RootView.deserialize(viewState)
|
||||
expect(rootView.find('.pane').length).toBe 1
|
||||
expect(rootView.find('.pane').children().length).toBe 0
|
||||
|
||||
describe "focus", ->
|
||||
describe "when there is an active editor", ->
|
||||
it "hands off focus to the active editor", ->
|
||||
rootView.attachToDom()
|
||||
|
||||
rootView.open() # create an editor
|
||||
expect(rootView).not.toMatchSelector(':focus')
|
||||
expect(rootView.getActiveEditor().isFocused).toBeTruthy()
|
||||
|
||||
describe "when there is an active view", ->
|
||||
it "hands off focus to the active view", ->
|
||||
editor = rootView.getActiveView()
|
||||
editor.isFocused = false
|
||||
rootView.focus()
|
||||
expect(rootView).not.toMatchSelector(':focus')
|
||||
expect(rootView.getActiveEditor().isFocused).toBeTruthy()
|
||||
expect(editor.isFocused).toBeTruthy()
|
||||
|
||||
describe "when there is no active editor", ->
|
||||
describe "when there is no active view", ->
|
||||
beforeEach ->
|
||||
rootView.getActiveEditor().remove()
|
||||
rootView.getActivePane().remove()
|
||||
expect(rootView.getActiveView()).toBeUndefined()
|
||||
rootView.attachToDom()
|
||||
expect(document.activeElement).toBe document.body
|
||||
|
||||
describe "when are visible focusable elements (with a -1 tabindex)", ->
|
||||
it "passes focus to the first focusable element", ->
|
||||
rootView.horizontal.append $$ ->
|
||||
@div "One", id: 'one', tabindex: -1
|
||||
@div "Two", id: 'two', tabindex: -1
|
||||
focusable1 = $$ -> @div "One", id: 'one', tabindex: -1
|
||||
focusable2 = $$ -> @div "Two", id: 'two', tabindex: -1
|
||||
rootView.horizontal.append(focusable1, focusable2)
|
||||
expect(document.activeElement).toBe document.body
|
||||
|
||||
rootView.focus()
|
||||
expect(rootView).not.toMatchSelector(':focus')
|
||||
expect(rootView.find('#one')).toMatchSelector(':focus')
|
||||
expect(rootView.find('#two')).not.toMatchSelector(':focus')
|
||||
expect(document.activeElement).toBe focusable1[0]
|
||||
|
||||
describe "when there are no visible focusable elements", ->
|
||||
it "surrenders focus to the body", ->
|
||||
expect(document.activeElement).toBe $('body')[0]
|
||||
focusable = $$ -> @div "One", id: 'one', tabindex: -1
|
||||
rootView.horizontal.append(focusable)
|
||||
focusable.hide()
|
||||
expect(document.activeElement).toBe document.body
|
||||
|
||||
describe "panes", ->
|
||||
[pane1, newPaneContent] = []
|
||||
|
||||
beforeEach ->
|
||||
rootView.attachToDom()
|
||||
rootView.width(800)
|
||||
rootView.height(600)
|
||||
pane1 = rootView.find('.pane').view()
|
||||
pane1.attr('id', 'pane-1')
|
||||
newPaneContent = $("<div>New pane content</div>")
|
||||
spyOn(newPaneContent, 'focus')
|
||||
|
||||
describe "vertical splits", ->
|
||||
describe "when .splitRight(view) is called on a pane", ->
|
||||
it "places a new pane to the right of the current pane in a .row div", ->
|
||||
expect(rootView.panes.find('.row')).not.toExist()
|
||||
|
||||
pane2 = pane1.splitRight(newPaneContent)
|
||||
expect(newPaneContent.focus).toHaveBeenCalled()
|
||||
|
||||
expect(rootView.panes.find('.row')).toExist()
|
||||
expect(rootView.panes.find('.row .pane').length).toBe 2
|
||||
[leftPane, rightPane] = rootView.panes.find('.row .pane').map -> $(this)
|
||||
expect(rightPane[0]).toBe pane2[0]
|
||||
expect(leftPane.attr('id')).toBe 'pane-1'
|
||||
expect(rightPane.html()).toBe "<div>New pane content</div>"
|
||||
|
||||
expectedColumnWidth = Math.floor(rootView.panes.width() / 2)
|
||||
expect(leftPane.outerWidth()).toBe expectedColumnWidth
|
||||
expect(rightPane.position().left).toBe expectedColumnWidth
|
||||
expect(rightPane.outerWidth()).toBe expectedColumnWidth
|
||||
|
||||
pane2.remove()
|
||||
|
||||
expect(rootView.panes.find('.row')).not.toExist()
|
||||
expect(rootView.panes.find('.pane').length).toBe 1
|
||||
expect(pane1.outerWidth()).toBe rootView.panes.width()
|
||||
|
||||
describe "when splitLeft(view) is called on a pane", ->
|
||||
it "places a new pane to the left of the current pane in a .row div", ->
|
||||
expect(rootView.find('.row')).not.toExist()
|
||||
|
||||
pane2 = pane1.splitLeft(newPaneContent)
|
||||
expect(newPaneContent.focus).toHaveBeenCalled()
|
||||
|
||||
expect(rootView.find('.row')).toExist()
|
||||
expect(rootView.find('.row .pane').length).toBe 2
|
||||
[leftPane, rightPane] = rootView.find('.row .pane').map -> $(this)
|
||||
expect(leftPane[0]).toBe pane2[0]
|
||||
expect(rightPane.attr('id')).toBe 'pane-1'
|
||||
expect(leftPane.html()).toBe "<div>New pane content</div>"
|
||||
|
||||
expectedColumnWidth = Math.floor(rootView.panes.width() / 2)
|
||||
expect(leftPane.outerWidth()).toBe expectedColumnWidth
|
||||
expect(rightPane.position().left).toBe expectedColumnWidth
|
||||
expect(rightPane.outerWidth()).toBe expectedColumnWidth
|
||||
|
||||
pane2.remove()
|
||||
|
||||
expect(rootView.panes.find('.row')).not.toExist()
|
||||
expect(rootView.panes.find('.pane').length).toBe 1
|
||||
expect(pane1.outerWidth()).toBe rootView.panes.width()
|
||||
expect(pane1.position().left).toBe 0
|
||||
|
||||
describe "horizontal splits", ->
|
||||
describe "when splitUp(view) is called on a pane", ->
|
||||
it "places a new pane above the current pane in a .column div", ->
|
||||
expect(rootView.find('.column')).not.toExist()
|
||||
|
||||
pane2 = pane1.splitUp(newPaneContent)
|
||||
expect(newPaneContent.focus).toHaveBeenCalled()
|
||||
|
||||
expect(rootView.find('.column')).toExist()
|
||||
expect(rootView.find('.column .pane').length).toBe 2
|
||||
[topPane, bottomPane] = rootView.find('.column .pane').map -> $(this)
|
||||
expect(topPane[0]).toBe pane2[0]
|
||||
expect(bottomPane.attr('id')).toBe 'pane-1'
|
||||
expect(topPane.html()).toBe "<div>New pane content</div>"
|
||||
|
||||
expectedRowHeight = Math.floor(rootView.panes.height() / 2)
|
||||
expect(topPane.outerHeight()).toBe expectedRowHeight
|
||||
expect(bottomPane.position().top).toBe expectedRowHeight
|
||||
expect(bottomPane.outerHeight()).toBe expectedRowHeight
|
||||
|
||||
pane2.remove()
|
||||
|
||||
expect(rootView.panes.find('.column')).not.toExist()
|
||||
expect(rootView.panes.find('.pane').length).toBe 1
|
||||
expect(pane1.outerHeight()).toBe rootView.panes.height()
|
||||
expect(pane1.position().top).toBe 0
|
||||
|
||||
describe "when splitDown(view) is called on a pane", ->
|
||||
it "places a new pane below the current pane in a .column div", ->
|
||||
expect(rootView.find('.column')).not.toExist()
|
||||
|
||||
pane2 = pane1.splitDown(newPaneContent)
|
||||
expect(newPaneContent.focus).toHaveBeenCalled()
|
||||
|
||||
expect(rootView.find('.column')).toExist()
|
||||
expect(rootView.find('.column .pane').length).toBe 2
|
||||
[topPane, bottomPane] = rootView.find('.column .pane').map -> $(this)
|
||||
expect(bottomPane[0]).toBe pane2[0]
|
||||
expect(topPane.attr('id')).toBe 'pane-1'
|
||||
expect(bottomPane.html()).toBe "<div>New pane content</div>"
|
||||
|
||||
expectedRowHeight = Math.floor(rootView.panes.height() / 2)
|
||||
expect(topPane.outerHeight()).toBe expectedRowHeight
|
||||
expect(bottomPane.position().top).toBe expectedRowHeight
|
||||
expect(bottomPane.outerHeight()).toBe expectedRowHeight
|
||||
|
||||
pane2.remove()
|
||||
|
||||
expect(rootView.panes.find('.column')).not.toExist()
|
||||
expect(rootView.panes.find('.pane').length).toBe 1
|
||||
expect(pane1.outerHeight()).toBe rootView.panes.height()
|
||||
|
||||
describe "layout of nested vertical and horizontal splits", ->
|
||||
it "lays out rows and columns with a consistent width", ->
|
||||
pane1.html("1")
|
||||
|
||||
pane1
|
||||
.splitLeft("2")
|
||||
.splitUp("3")
|
||||
.splitLeft("4")
|
||||
.splitDown("5")
|
||||
|
||||
row1 = rootView.panes.children(':eq(0)')
|
||||
expect(row1.children().length).toBe 2
|
||||
column1 = row1.children(':eq(0)').view()
|
||||
pane1 = row1.children(':eq(1)').view()
|
||||
expect(column1.outerWidth()).toBe Math.round(2/3 * rootView.panes.width())
|
||||
expect(column1.outerHeight()).toBe rootView.height()
|
||||
expect(pane1.outerWidth()).toBe Math.round(1/3 * rootView.panes.width())
|
||||
expect(pane1.outerHeight()).toBe rootView.height()
|
||||
expect(Math.round(pane1.position().left)).toBe column1.outerWidth()
|
||||
|
||||
expect(column1.children().length).toBe 2
|
||||
row2 = column1.children(':eq(0)').view()
|
||||
pane2 = column1.children(':eq(1)').view()
|
||||
expect(row2.outerWidth()).toBe column1.outerWidth()
|
||||
expect(row2.height()).toBe 2/3 * rootView.panes.height()
|
||||
expect(pane2.outerWidth()).toBe column1.outerWidth()
|
||||
expect(pane2.outerHeight()).toBe 1/3 * rootView.panes.height()
|
||||
expect(pane2.position().top).toBe row2.height()
|
||||
|
||||
expect(row2.children().length).toBe 2
|
||||
column3 = row2.children(':eq(0)').view()
|
||||
pane3 = row2.children(':eq(1)').view()
|
||||
expect(column3.outerWidth()).toBe Math.round(1/3 * rootView.panes.width())
|
||||
expect(column3.outerHeight()).toBe row2.outerHeight()
|
||||
# the built in rounding seems to be rounding x.5 down, but we need to go up. this sucks.
|
||||
expect(Math.round(pane3.trueWidth())).toBe Math.round(1/3 * rootView.panes.width())
|
||||
expect(pane3.height()).toBe row2.outerHeight()
|
||||
expect(Math.round(pane3.position().left)).toBe column3.width()
|
||||
|
||||
expect(column3.children().length).toBe 2
|
||||
pane4 = column3.children(':eq(0)').view()
|
||||
pane5 = column3.children(':eq(1)').view()
|
||||
expect(pane4.outerWidth()).toBe column3.width()
|
||||
expect(pane4.outerHeight()).toBe 1/3 * rootView.panes.height()
|
||||
expect(pane5.outerWidth()).toBe column3.width()
|
||||
expect(pane5.position().top).toBe pane4.outerHeight()
|
||||
expect(pane5.outerHeight()).toBe 1/3 * rootView.panes.height()
|
||||
|
||||
pane5.remove()
|
||||
|
||||
expect(column3.parent()).not.toExist()
|
||||
expect(pane2.outerHeight()).toBe Math.floor(1/2 * rootView.panes.height())
|
||||
expect(pane3.outerHeight()).toBe Math.floor(1/2 * rootView.panes.height())
|
||||
expect(pane4.outerHeight()).toBe Math.floor(1/2 * rootView.panes.height())
|
||||
|
||||
pane4.remove()
|
||||
expect(row2.parent()).not.toExist()
|
||||
expect(pane1.outerWidth()).toBe Math.floor(1/2 * rootView.panes.width())
|
||||
expect(pane2.outerWidth()).toBe Math.floor(1/2 * rootView.panes.width())
|
||||
expect(pane3.outerWidth()).toBe Math.floor(1/2 * rootView.panes.width())
|
||||
|
||||
pane3.remove()
|
||||
expect(column1.parent()).not.toExist()
|
||||
expect(pane2.outerHeight()).toBe rootView.panes.height()
|
||||
|
||||
pane2.remove()
|
||||
expect(row1.parent()).not.toExist()
|
||||
expect(rootView.panes.children().length).toBe 1
|
||||
expect(rootView.panes.children('.pane').length).toBe 1
|
||||
expect(pane1.outerWidth()).toBe rootView.panes.width()
|
||||
|
||||
describe ".focusNextPane()", ->
|
||||
it "focuses the wrapped view of the pane after the currently focused pane", ->
|
||||
class DummyView extends View
|
||||
@content: (number) -> @div(number, tabindex: -1)
|
||||
|
||||
view1 = pane1.wrappedView
|
||||
view2 = new DummyView(2)
|
||||
view3 = new DummyView(3)
|
||||
pane2 = pane1.splitDown(view2)
|
||||
pane3 = pane2.splitRight(view3)
|
||||
rootView.attachToDom()
|
||||
view1.focus()
|
||||
|
||||
spyOn(view1, 'focus').andCallThrough()
|
||||
spyOn(view2, 'focus').andCallThrough()
|
||||
spyOn(view3, 'focus').andCallThrough()
|
||||
|
||||
rootView.focusNextPane()
|
||||
expect(view2.focus).toHaveBeenCalled()
|
||||
rootView.focusNextPane()
|
||||
expect(view3.focus).toHaveBeenCalled()
|
||||
rootView.focusNextPane()
|
||||
expect(view1.focus).toHaveBeenCalled()
|
||||
rootView.focus()
|
||||
expect(document.activeElement).toBe document.body
|
||||
|
||||
describe "keymap wiring", ->
|
||||
commandHandler = null
|
||||
@@ -360,249 +140,144 @@ describe "RootView", ->
|
||||
|
||||
window.keymap.bindKeys('*', 'x': 'foo-command')
|
||||
|
||||
describe "when a keydown event is triggered on the RootView (not originating from Ace)", ->
|
||||
describe "when a keydown event is triggered on the RootView", ->
|
||||
it "triggers matching keybindings for that event", ->
|
||||
event = keydownEvent 'x', target: rootView[0]
|
||||
|
||||
rootView.trigger(event)
|
||||
expect(commandHandler).toHaveBeenCalled()
|
||||
|
||||
describe ".activeKeybindings()", ->
|
||||
originalKeymap = null
|
||||
keymap = null
|
||||
editor = null
|
||||
describe "window title", ->
|
||||
describe "when the project has no path", ->
|
||||
it "sets the title to 'untitled'", ->
|
||||
project.setPath(undefined)
|
||||
expect(rootView.title).toBe 'untitled'
|
||||
|
||||
describe "when the project has a path", ->
|
||||
beforeEach ->
|
||||
rootView.attachToDom()
|
||||
editor = rootView.getActiveEditor()
|
||||
keymap = new (require 'keymap')
|
||||
originalKeymap = window.keymap
|
||||
window.keymap = keymap
|
||||
rootView.open('b')
|
||||
|
||||
afterEach ->
|
||||
window.keymap = originalKeymap
|
||||
describe "when there is an active pane item", ->
|
||||
it "sets the title to the pane item's title plus the project path", ->
|
||||
item = rootView.getActivePaneItem()
|
||||
expect(rootView.title).toBe "#{item.getTitle()} - #{project.getPath()}"
|
||||
|
||||
it "returns all keybindings available for focused element", ->
|
||||
editor.on 'test-event-a', => # nothing
|
||||
describe "when the title of the active pane item changes", ->
|
||||
it "updates the window title based on the item's new title", ->
|
||||
editSession = rootView.getActivePaneItem()
|
||||
editSession.buffer.setPath('/tmp/hi')
|
||||
expect(rootView.title).toBe "#{editSession.getTitle()} - #{project.getPath()}"
|
||||
|
||||
keymap.bindKeys ".editor",
|
||||
"meta-a": "test-event-a"
|
||||
"meta-b": "test-event-b"
|
||||
describe "when the active pane's item changes", ->
|
||||
it "updates the title to the new item's title plus the project path", ->
|
||||
rootView.getActivePane().showNextItem()
|
||||
item = rootView.getActivePaneItem()
|
||||
expect(rootView.title).toBe "#{item.getTitle()} - #{project.getPath()}"
|
||||
|
||||
keybindings = rootView.activeKeybindings()
|
||||
expect(Object.keys(keybindings).length).toBe 2
|
||||
expect(keybindings["meta-a"]).toEqual "test-event-a"
|
||||
describe "when the last pane item is removed", ->
|
||||
it "sets the title to the project's path", ->
|
||||
rootView.getActivePane().remove()
|
||||
expect(rootView.getActivePaneItem()).toBeUndefined()
|
||||
expect(rootView.title).toBe project.getPath()
|
||||
|
||||
describe "when the path of the active editor changes", ->
|
||||
it "changes the title and emits an root-view:active-path-changed event", ->
|
||||
pathChangeHandler = jasmine.createSpy 'pathChangeHandler'
|
||||
rootView.on 'root-view:active-path-changed', pathChangeHandler
|
||||
|
||||
editor1 = rootView.getActiveEditor()
|
||||
expect(rootView.getTitle()).toBe "#{fs.base(editor1.getPath())} – #{project.getPath()}"
|
||||
|
||||
editor2 = rootView.getActiveEditor().splitLeft()
|
||||
|
||||
path = project.resolve('b')
|
||||
editor2.edit(project.buildEditSessionForPath(path))
|
||||
expect(pathChangeHandler).toHaveBeenCalled()
|
||||
expect(rootView.getTitle()).toBe "#{fs.base(editor2.getPath())} – #{project.getPath()}"
|
||||
|
||||
pathChangeHandler.reset()
|
||||
editor1.getBuffer().saveAs("/tmp/should-not-be-title.txt")
|
||||
expect(pathChangeHandler).not.toHaveBeenCalled()
|
||||
expect(rootView.getTitle()).toBe "#{fs.base(editor2.getPath())} – #{project.getPath()}"
|
||||
|
||||
it "sets the project path to the directory of the editor if it was previously unassigned", ->
|
||||
project.setPath(undefined)
|
||||
window.rootView = new RootView
|
||||
rootView.open()
|
||||
expect(project.getPath()?).toBeFalsy()
|
||||
rootView.getActiveEditor().getBuffer().saveAs('/tmp/ignore-me')
|
||||
expect(project.getPath()).toBe '/tmp'
|
||||
|
||||
describe "when editors are focused", ->
|
||||
it "triggers 'root-view:active-path-changed' events if the path of the active editor actually changes", ->
|
||||
pathChangeHandler = jasmine.createSpy 'pathChangeHandler'
|
||||
rootView.on 'root-view:active-path-changed', pathChangeHandler
|
||||
|
||||
editor1 = rootView.getActiveEditor()
|
||||
editor2 = rootView.getActiveEditor().splitLeft()
|
||||
|
||||
rootView.open(require.resolve('fixtures/sample.txt'))
|
||||
expect(pathChangeHandler).toHaveBeenCalled()
|
||||
pathChangeHandler.reset()
|
||||
|
||||
editor1.focus()
|
||||
expect(pathChangeHandler).toHaveBeenCalled()
|
||||
pathChangeHandler.reset()
|
||||
|
||||
rootView.focus()
|
||||
expect(pathChangeHandler).not.toHaveBeenCalled()
|
||||
|
||||
editor2.edit(editor1.activeEditSession.copy())
|
||||
editor2.focus()
|
||||
expect(pathChangeHandler).not.toHaveBeenCalled()
|
||||
|
||||
describe "when the last editor is removed", ->
|
||||
it "updates the title to the project path", ->
|
||||
rootView.getEditors()[0].remove()
|
||||
expect(rootView.getTitle()).toBe project.getPath()
|
||||
describe "when an inactive pane's item changes", ->
|
||||
it "does not update the title", ->
|
||||
pane = rootView.getActivePane()
|
||||
pane.splitRight()
|
||||
initialTitle = rootView.title
|
||||
pane.showNextItem()
|
||||
expect(rootView.title).toBe initialTitle
|
||||
|
||||
describe "font size adjustment", ->
|
||||
editor = null
|
||||
beforeEach ->
|
||||
editor = rootView.getActiveEditor()
|
||||
editor.attachToDom()
|
||||
|
||||
it "increases/decreases font size when increase/decrease-font-size events are triggered", ->
|
||||
fontSizeBefore = editor.getFontSize()
|
||||
fontSizeBefore = config.get('editor.fontSize')
|
||||
rootView.trigger 'window:increase-font-size'
|
||||
expect(editor.getFontSize()).toBe fontSizeBefore + 1
|
||||
expect(config.get('editor.fontSize')).toBe fontSizeBefore + 1
|
||||
rootView.trigger 'window:increase-font-size'
|
||||
expect(editor.getFontSize()).toBe fontSizeBefore + 2
|
||||
expect(config.get('editor.fontSize')).toBe fontSizeBefore + 2
|
||||
rootView.trigger 'window:decrease-font-size'
|
||||
expect(editor.getFontSize()).toBe fontSizeBefore + 1
|
||||
expect(config.get('editor.fontSize')).toBe fontSizeBefore + 1
|
||||
rootView.trigger 'window:decrease-font-size'
|
||||
expect(editor.getFontSize()).toBe fontSizeBefore
|
||||
expect(config.get('editor.fontSize')).toBe fontSizeBefore
|
||||
|
||||
it "does not allow the font size to be less than 1", ->
|
||||
config.set("editor.fontSize", 1)
|
||||
rootView.trigger 'window:decrease-font-size'
|
||||
expect(editor.getFontSize()).toBe 1
|
||||
expect(config.get('editor.fontSize')).toBe 1
|
||||
|
||||
describe ".open(path, options)", ->
|
||||
describe "when there is no active editor", ->
|
||||
describe "when there is no active pane", ->
|
||||
beforeEach ->
|
||||
rootView.getActiveEditor().destroyActiveEditSession()
|
||||
expect(rootView.getActiveEditor()).toBeUndefined()
|
||||
spyOn(Pane.prototype, 'focus')
|
||||
rootView.getActivePane().remove()
|
||||
expect(rootView.getActivePane()).toBeUndefined()
|
||||
|
||||
describe "when called with no path", ->
|
||||
it "opens / returns an edit session for an empty buffer in a new editor", ->
|
||||
it "creates a empty edit session as an item on a new pane, and focuses the pane", ->
|
||||
editSession = rootView.open()
|
||||
expect(rootView.getActiveEditor()).toBeDefined()
|
||||
expect(rootView.getActiveEditor().getPath()).toBeUndefined()
|
||||
expect(editSession).toBe rootView.getActiveEditor().activeEditSession
|
||||
expect(rootView.getActivePane().activeItem).toBe editSession
|
||||
expect(editSession.getPath()).toBeUndefined()
|
||||
expect(rootView.getActivePane().focus).toHaveBeenCalled()
|
||||
|
||||
describe "when called with a path", ->
|
||||
it "opens a buffer with the given path in a new editor", ->
|
||||
it "creates an edit session for the given path as an item on a new pane, and focuses the pane", ->
|
||||
editSession = rootView.open('b')
|
||||
expect(rootView.getActiveEditor()).toBeDefined()
|
||||
expect(rootView.getActiveEditor().getPath()).toBe require.resolve('fixtures/dir/b')
|
||||
expect(editSession).toBe rootView.getActiveEditor().activeEditSession
|
||||
expect(rootView.getActivePane().activeItem).toBe editSession
|
||||
expect(editSession.getPath()).toBe require.resolve('fixtures/dir/b')
|
||||
expect(rootView.getActivePane().focus).toHaveBeenCalled()
|
||||
|
||||
describe "when there is an active editor", ->
|
||||
describe "when the changeFocus option is false", ->
|
||||
it "does not focus the new pane", ->
|
||||
editSession = rootView.open('b', changeFocus: false)
|
||||
expect(rootView.getActivePane().focus).not.toHaveBeenCalled()
|
||||
|
||||
describe "when there is an active pane", ->
|
||||
[activePane, initialItemCount] = []
|
||||
beforeEach ->
|
||||
expect(rootView.getActiveEditor()).toBeDefined()
|
||||
activePane = rootView.getActivePane()
|
||||
spyOn(activePane, 'focus')
|
||||
initialItemCount = activePane.getItems().length
|
||||
|
||||
describe "when called with no path", ->
|
||||
it "opens an empty buffer in the active editor", ->
|
||||
it "opens an edit session with an empty buffer as an item on the active pane and focuses it", ->
|
||||
editSession = rootView.open()
|
||||
expect(rootView.getActiveEditor().getPath()).toBeUndefined()
|
||||
expect(editSession).toBe rootView.getActiveEditor().activeEditSession
|
||||
expect(activePane.getItems().length).toBe initialItemCount + 1
|
||||
expect(activePane.activeItem).toBe editSession
|
||||
expect(editSession.getPath()).toBeUndefined()
|
||||
expect(activePane.focus).toHaveBeenCalled()
|
||||
|
||||
describe "when called with a path", ->
|
||||
[editor1, editor2] = []
|
||||
beforeEach ->
|
||||
rootView.attachToDom()
|
||||
editor1 = rootView.getActiveEditor()
|
||||
editor2 = editor1.splitRight()
|
||||
rootView.open('b')
|
||||
editor2.loadPreviousEditSession()
|
||||
editor1.focus()
|
||||
describe "when the active pane already has an edit session item for the path being opened", ->
|
||||
it "shows the existing edit session on the pane", ->
|
||||
previousEditSession = activePane.activeItem
|
||||
|
||||
describe "when allowActiveEditorChange is false (the default)", ->
|
||||
activeEditor = null
|
||||
beforeEach ->
|
||||
activeEditor = rootView.getActiveEditor()
|
||||
editSession = rootView.open('b')
|
||||
expect(activePane.activeItem).toBe editSession
|
||||
expect(editSession).not.toBe previousEditSession
|
||||
|
||||
describe "when the active editor has an edit session for the given path", ->
|
||||
it "re-activates the existing edit session", ->
|
||||
expect(activeEditor.getPath()).toBe require.resolve('fixtures/dir/a')
|
||||
previousEditSession = activeEditor.activeEditSession
|
||||
editSession = rootView.open(previousEditSession.getPath())
|
||||
expect(editSession).toBe previousEditSession
|
||||
expect(activePane.activeItem).toBe editSession
|
||||
|
||||
editSession = rootView.open('b')
|
||||
expect(activeEditor.activeEditSession).not.toBe previousEditSession
|
||||
expect(editSession).toBe rootView.getActiveEditor().activeEditSession
|
||||
expect(activePane.focus).toHaveBeenCalled()
|
||||
|
||||
editSession = rootView.open('a')
|
||||
expect(activeEditor.activeEditSession).toBe previousEditSession
|
||||
expect(editSession).toBe previousEditSession
|
||||
describe "when the active pane does not have an edit session item for the path being opened", ->
|
||||
it "creates a new edit session for the given path in the active editor", ->
|
||||
editSession = rootView.open('b')
|
||||
expect(activePane.items.length).toBe 2
|
||||
expect(activePane.activeItem).toBe editSession
|
||||
expect(activePane.focus).toHaveBeenCalled()
|
||||
|
||||
describe "when the active editor does not have an edit session for the given path", ->
|
||||
it "creates a new edit session for the given path in the active editor", ->
|
||||
editSession = rootView.open('b')
|
||||
expect(activeEditor.editSessions.length).toBe 2
|
||||
expect(editSession).toBe rootView.getActiveEditor().activeEditSession
|
||||
|
||||
describe "when the 'allowActiveEditorChange' option is true", ->
|
||||
describe "when the active editor has an edit session for the given path", ->
|
||||
it "re-activates the existing edit session regardless of whether any other editor also has an edit session for the path", ->
|
||||
activeEditor = rootView.getActiveEditor()
|
||||
expect(activeEditor.getPath()).toBe require.resolve('fixtures/dir/a')
|
||||
previousEditSession = activeEditor.activeEditSession
|
||||
|
||||
editSession = rootView.open('b')
|
||||
expect(activeEditor.activeEditSession).not.toBe previousEditSession
|
||||
expect(editSession).toBe activeEditor.activeEditSession
|
||||
|
||||
editSession = rootView.open('a', allowActiveEditorChange: true)
|
||||
expect(activeEditor.activeEditSession).toBe previousEditSession
|
||||
expect(editSession).toBe activeEditor.activeEditSession
|
||||
|
||||
describe "when the active editor does *not* have an edit session for the given path", ->
|
||||
describe "when another editor has an edit session for the path", ->
|
||||
it "focuses the other editor and activates its edit session for the path", ->
|
||||
expect(rootView.getActiveEditor()).toBe editor1
|
||||
editSession = rootView.open('b', allowActiveEditorChange: true)
|
||||
expect(rootView.getActiveEditor()).toBe editor2
|
||||
expect(editor2.getPath()).toBe require.resolve('fixtures/dir/b')
|
||||
expect(editSession).toBe rootView.getActiveEditor().activeEditSession
|
||||
|
||||
describe "when no other editor has an edit session for the path either", ->
|
||||
it "creates a new edit session for the path on the current active editor", ->
|
||||
path = require.resolve('fixtures/sample.js')
|
||||
editSession = rootView.open(path, allowActiveEditorChange: true)
|
||||
expect(rootView.getActiveEditor()).toBe editor1
|
||||
expect(editor1.getPath()).toBe path
|
||||
expect(editSession).toBe rootView.getActiveEditor().activeEditSession
|
||||
|
||||
describe ".saveAll()", ->
|
||||
it "saves all open editors", ->
|
||||
project.setPath('/tmp')
|
||||
file1 = '/tmp/atom-temp1.txt'
|
||||
file2 = '/tmp/atom-temp2.txt'
|
||||
fs.write(file1, "file1")
|
||||
fs.write(file2, "file2")
|
||||
rootView.open(file1)
|
||||
|
||||
editor1 = rootView.getActiveEditor()
|
||||
buffer1 = editor1.activeEditSession.buffer
|
||||
expect(buffer1.getText()).toBe("file1")
|
||||
expect(buffer1.isModified()).toBe(false)
|
||||
buffer1.setText('edited1')
|
||||
expect(buffer1.isModified()).toBe(true)
|
||||
|
||||
editor2 = editor1.splitRight()
|
||||
editor2.edit(project.buildEditSessionForPath('atom-temp2.txt'))
|
||||
buffer2 = editor2.activeEditSession.buffer
|
||||
expect(buffer2.getText()).toBe("file2")
|
||||
expect(buffer2.isModified()).toBe(false)
|
||||
buffer2.setText('edited2')
|
||||
expect(buffer2.isModified()).toBe(true)
|
||||
|
||||
rootView.saveAll()
|
||||
|
||||
expect(buffer1.isModified()).toBe(false)
|
||||
expect(fs.read(buffer1.getPath())).toBe("edited1")
|
||||
expect(buffer2.isModified()).toBe(false)
|
||||
expect(fs.read(buffer2.getPath())).toBe("edited2")
|
||||
describe "when the changeFocus option is false", ->
|
||||
it "does not focus the active pane", ->
|
||||
editSession = rootView.open('b', changeFocus: false)
|
||||
expect(activePane.focus).not.toHaveBeenCalled()
|
||||
|
||||
describe "window:toggle-invisibles event", ->
|
||||
it "shows/hides invisibles in all open and future editors", ->
|
||||
rootView.height(200)
|
||||
rootView.attachToDom()
|
||||
rightEditor = rootView.getActiveEditor()
|
||||
rightEditor = rootView.getActiveView()
|
||||
rightEditor.setText(" \t ")
|
||||
leftEditor = rightEditor.splitLeft()
|
||||
expect(rightEditor.find(".line:first").text()).toBe " "
|
||||
@@ -636,7 +311,7 @@ describe "RootView", ->
|
||||
count++
|
||||
rootView.eachEditor(callback)
|
||||
expect(count).toBe 1
|
||||
expect(callbackEditor).toBe rootView.getActiveEditor()
|
||||
expect(callbackEditor).toBe rootView.getActiveView()
|
||||
|
||||
it "invokes the callback for new editor", ->
|
||||
count = 0
|
||||
@@ -648,9 +323,9 @@ describe "RootView", ->
|
||||
rootView.eachEditor(callback)
|
||||
count = 0
|
||||
callbackEditor = null
|
||||
rootView.getActiveEditor().splitRight()
|
||||
rootView.getActiveView().splitRight()
|
||||
expect(count).toBe 1
|
||||
expect(callbackEditor).toBe rootView.getActiveEditor()
|
||||
expect(callbackEditor).toBe rootView.getActiveView()
|
||||
|
||||
describe ".eachBuffer(callback)", ->
|
||||
beforeEach ->
|
||||
@@ -664,7 +339,7 @@ describe "RootView", ->
|
||||
count++
|
||||
rootView.eachBuffer(callback)
|
||||
expect(count).toBe 1
|
||||
expect(callbackBuffer).toBe rootView.getActiveEditor().getBuffer()
|
||||
expect(callbackBuffer).toBe rootView.getActiveView().getBuffer()
|
||||
|
||||
it "invokes the callback for new buffer", ->
|
||||
count = 0
|
||||
@@ -678,4 +353,4 @@ describe "RootView", ->
|
||||
callbackBuffer = null
|
||||
rootView.open(require.resolve('fixtures/sample.txt'))
|
||||
expect(count).toBe 1
|
||||
expect(callbackBuffer).toBe rootView.getActiveEditor().getBuffer()
|
||||
expect(callbackBuffer).toBe rootView.getActiveView().getBuffer()
|
||||
|
||||
@@ -262,7 +262,7 @@ describe "TextMateGrammar", ->
|
||||
describe "when the grammar is CSON", ->
|
||||
it "loads the grammar and correctly parses a keyword", ->
|
||||
spyOn(syntax, 'addGrammar')
|
||||
pack = new TextMatePackage(fixturesProject.resolve("packages/package-with-a-cson-grammar.tmbundle"))
|
||||
pack = new TextMatePackage(project.resolve("packages/package-with-a-cson-grammar.tmbundle"))
|
||||
pack.load()
|
||||
grammar = pack.grammars[0]
|
||||
expect(grammar).toBeTruthy()
|
||||
|
||||
@@ -20,33 +20,38 @@ describe "@load(name)", ->
|
||||
expect($(".editor").css("background-color")).toBe("rgb(20, 20, 20)")
|
||||
|
||||
describe "AtomTheme", ->
|
||||
describe "when the theme is a file", ->
|
||||
it "loads and applies css", ->
|
||||
expect($(".editor").css("padding-bottom")).not.toBe "1234px"
|
||||
themePath = project.resolve('themes/theme-stylesheet.css')
|
||||
theme = Theme.load(themePath)
|
||||
expect($(".editor").css("padding-top")).toBe "1234px"
|
||||
|
||||
it "parses, loads and applies less", ->
|
||||
expect($(".editor").css("padding-bottom")).not.toBe "1234px"
|
||||
themePath = project.resolve('themes/theme-stylesheet.less')
|
||||
theme = Theme.load(themePath)
|
||||
expect($(".editor").css("padding-top")).toBe "4321px"
|
||||
|
||||
describe "when the theme contains a package.json file", ->
|
||||
it "loads and applies css from package.json in the correct order", ->
|
||||
it "loads and applies stylesheets from package.json in the correct order", ->
|
||||
expect($(".editor").css("padding-top")).not.toBe("101px")
|
||||
expect($(".editor").css("padding-right")).not.toBe("102px")
|
||||
expect($(".editor").css("padding-bottom")).not.toBe("103px")
|
||||
|
||||
themePath = fixturesProject.resolve('themes/theme-with-package-file')
|
||||
themePath = project.resolve('themes/theme-with-package-file')
|
||||
theme = Theme.load(themePath)
|
||||
expect($(".editor").css("padding-top")).toBe("101px")
|
||||
expect($(".editor").css("padding-right")).toBe("102px")
|
||||
expect($(".editor").css("padding-bottom")).toBe("103px")
|
||||
|
||||
describe "when the theme is a CSS file", ->
|
||||
it "loads and applies the stylesheet", ->
|
||||
expect($(".editor").css("padding-bottom")).not.toBe "1234px"
|
||||
|
||||
themePath = fixturesProject.resolve('themes/theme-stylesheet.css')
|
||||
theme = Theme.load(themePath)
|
||||
expect($(".editor").css("padding-top")).toBe "1234px"
|
||||
|
||||
describe "when the theme does not contain a package.json file and is a directory", ->
|
||||
it "loads all CSS files in the directory", ->
|
||||
it "loads all stylesheet files in the directory", ->
|
||||
expect($(".editor").css("padding-top")).not.toBe "10px"
|
||||
expect($(".editor").css("padding-right")).not.toBe "20px"
|
||||
expect($(".editor").css("padding-bottom")).not.toBe "30px"
|
||||
|
||||
themePath = fixturesProject.resolve('themes/theme-without-package-file')
|
||||
themePath = project.resolve('themes/theme-without-package-file')
|
||||
theme = Theme.load(themePath)
|
||||
expect($(".editor").css("padding-top")).toBe "10px"
|
||||
expect($(".editor").css("padding-right")).toBe "20px"
|
||||
|
||||
@@ -18,7 +18,7 @@ describe "TokenizedBuffer", ->
|
||||
|
||||
describe "when the buffer contains soft-tabs", ->
|
||||
beforeEach ->
|
||||
editSession = fixturesProject.buildEditSessionForPath('sample.js', autoIndent: false)
|
||||
editSession = project.buildEditSession('sample.js', autoIndent: false)
|
||||
buffer = editSession.buffer
|
||||
tokenizedBuffer = editSession.displayBuffer.tokenizedBuffer
|
||||
editSession.setVisible(true)
|
||||
@@ -299,7 +299,7 @@ describe "TokenizedBuffer", ->
|
||||
describe "when the buffer contains hard-tabs", ->
|
||||
beforeEach ->
|
||||
tabLength = 2
|
||||
editSession = fixturesProject.buildEditSessionForPath('sample-with-tabs.coffee', { tabLength })
|
||||
editSession = project.buildEditSession('sample-with-tabs.coffee', { tabLength })
|
||||
buffer = editSession.buffer
|
||||
tokenizedBuffer = editSession.displayBuffer.tokenizedBuffer
|
||||
editSession.setVisible(true)
|
||||
@@ -328,7 +328,7 @@ describe "TokenizedBuffer", ->
|
||||
|
||||
describe "when a Git commit message file is tokenized", ->
|
||||
beforeEach ->
|
||||
editSession = fixturesProject.buildEditSessionForPath('COMMIT_EDITMSG', autoIndent: false)
|
||||
editSession = project.buildEditSession('COMMIT_EDITMSG', autoIndent: false)
|
||||
buffer = editSession.buffer
|
||||
tokenizedBuffer = editSession.displayBuffer.tokenizedBuffer
|
||||
editSession.setVisible(true)
|
||||
@@ -355,7 +355,7 @@ describe "TokenizedBuffer", ->
|
||||
|
||||
describe "when a C++ source file is tokenized", ->
|
||||
beforeEach ->
|
||||
editSession = fixturesProject.buildEditSessionForPath('includes.cc', autoIndent: false)
|
||||
editSession = project.buildEditSession('includes.cc', autoIndent: false)
|
||||
buffer = editSession.buffer
|
||||
tokenizedBuffer = editSession.displayBuffer.tokenizedBuffer
|
||||
editSession.setVisible(true)
|
||||
@@ -386,7 +386,7 @@ describe "TokenizedBuffer", ->
|
||||
|
||||
describe "when a Ruby source file is tokenized", ->
|
||||
beforeEach ->
|
||||
editSession = fixturesProject.buildEditSessionForPath('hello.rb', autoIndent: false)
|
||||
editSession = project.buildEditSession('hello.rb', autoIndent: false)
|
||||
buffer = editSession.buffer
|
||||
tokenizedBuffer = editSession.displayBuffer.tokenizedBuffer
|
||||
editSession.setVisible(true)
|
||||
@@ -403,7 +403,7 @@ describe "TokenizedBuffer", ->
|
||||
|
||||
describe "when an Objective-C source file is tokenized", ->
|
||||
beforeEach ->
|
||||
editSession = fixturesProject.buildEditSessionForPath('function.mm', autoIndent: false)
|
||||
editSession = project.buildEditSession('function.mm', autoIndent: false)
|
||||
buffer = editSession.buffer
|
||||
tokenizedBuffer = editSession.displayBuffer.tokenizedBuffer
|
||||
editSession.setVisible(true)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
$ = require 'jquery'
|
||||
fs = require 'fs'
|
||||
{less} = require 'less'
|
||||
|
||||
describe "Window", ->
|
||||
projectPath = null
|
||||
@@ -34,49 +35,89 @@ describe "Window", ->
|
||||
$(window).trigger 'focus'
|
||||
expect($("body")).not.toHaveClass("is-blurred")
|
||||
|
||||
describe ".close()", ->
|
||||
it "is triggered by the 'core:close' event", ->
|
||||
spyOn window, 'close'
|
||||
$(window).trigger 'core:close'
|
||||
expect(window.close).toHaveBeenCalled()
|
||||
describe "window:close event", ->
|
||||
describe "when no pane items are modified", ->
|
||||
it "calls window.close", ->
|
||||
spyOn window, 'close'
|
||||
$(window).trigger 'window:close'
|
||||
expect(window.close).toHaveBeenCalled()
|
||||
|
||||
it "is triggered by the 'window:close event'", ->
|
||||
spyOn window, 'close'
|
||||
$(window).trigger 'window:close'
|
||||
expect(window.close).toHaveBeenCalled()
|
||||
describe "when pane items are are modified", ->
|
||||
it "prompts user to save and and calls window.close", ->
|
||||
spyOn(window, 'close')
|
||||
spyOn(atom, "confirm").andCallFake (a, b, c, d, e, f, g, noSave) -> noSave()
|
||||
editSession = rootView.open("sample.js")
|
||||
editSession.insertText("I look different, I feel different.")
|
||||
$(window).trigger 'window:close'
|
||||
expect(window.close).toHaveBeenCalled()
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
|
||||
it "prompts user to save and aborts if dialog is canceled", ->
|
||||
spyOn(window, 'close')
|
||||
spyOn(atom, "confirm").andCallFake (a, b, c, d, e, cancel) -> cancel()
|
||||
editSession = rootView.open("sample.js")
|
||||
editSession.insertText("I look different, I feel different.")
|
||||
$(window).trigger 'window:close'
|
||||
expect(window.close).not.toHaveBeenCalled()
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
|
||||
describe ".reload()", ->
|
||||
it "returns false when no buffers are modified", ->
|
||||
beforeEach ->
|
||||
spyOn($native, "reload")
|
||||
|
||||
it "returns false when no buffers are modified", ->
|
||||
window.reload()
|
||||
expect($native.reload).toHaveBeenCalled()
|
||||
|
||||
it "shows alert when a modifed buffer exists", ->
|
||||
it "shows an alert when a modifed buffer exists", ->
|
||||
rootView.open('sample.js')
|
||||
rootView.getActiveEditor().insertText("hi")
|
||||
rootView.getActiveView().insertText("hi")
|
||||
spyOn(atom, "confirm")
|
||||
spyOn($native, "reload")
|
||||
window.reload()
|
||||
expect($native.reload).not.toHaveBeenCalled()
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
|
||||
describe "requireStylesheet(path)", ->
|
||||
it "synchronously loads the stylesheet at the given path and installs a style tag for it in the head", ->
|
||||
$('head style[id*="atom.css"]').remove()
|
||||
it "synchronously loads css at the given path and installs a style tag for it in the head", ->
|
||||
cssPath = project.resolve('css.css')
|
||||
lengthBefore = $('head style').length
|
||||
requireStylesheet('atom.css')
|
||||
|
||||
requireStylesheet(cssPath)
|
||||
expect($('head style').length).toBe lengthBefore + 1
|
||||
|
||||
styleElt = $('head style[id*="atom.css"]')
|
||||
|
||||
fullPath = require.resolve('atom.css')
|
||||
expect(styleElt.attr('id')).toBe fullPath
|
||||
expect(styleElt.text()).toBe fs.read(fullPath)
|
||||
element = $('head style[id*="css.css"]')
|
||||
expect(element.attr('id')).toBe cssPath
|
||||
expect(element.text()).toBe fs.read(cssPath)
|
||||
|
||||
# doesn't append twice
|
||||
requireStylesheet('atom.css')
|
||||
requireStylesheet(cssPath)
|
||||
expect($('head style').length).toBe lengthBefore + 1
|
||||
|
||||
$('head style[id*="css.css"]').remove()
|
||||
|
||||
it "synchronously loads and parses less files at the given path and installs a style tag for it in the head", ->
|
||||
lessPath = project.resolve('sample.less')
|
||||
lengthBefore = $('head style').length
|
||||
requireStylesheet(lessPath)
|
||||
expect($('head style').length).toBe lengthBefore + 1
|
||||
|
||||
element = $('head style[id*="sample.less"]')
|
||||
expect(element.attr('id')).toBe lessPath
|
||||
expect(element.text()).toBe """
|
||||
#header {
|
||||
color: #4d926f;
|
||||
}
|
||||
h2 {
|
||||
color: #4d926f;
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
# doesn't append twice
|
||||
requireStylesheet(lessPath)
|
||||
expect($('head style').length).toBe lengthBefore + 1
|
||||
$('head style[id*="sample.less"]').remove()
|
||||
|
||||
describe ".disableStyleSheet(path)", ->
|
||||
it "removes styling applied by given stylesheet path", ->
|
||||
cssPath = require.resolve(fs.join("fixtures", "css.css"))
|
||||
@@ -103,13 +144,13 @@ describe "Window", ->
|
||||
|
||||
it "unsubscribes from all buffers", ->
|
||||
rootView.open('sample.js')
|
||||
editor1 = rootView.getActiveEditor()
|
||||
editor2 = editor1.splitRight()
|
||||
expect(window.rootView.getEditors().length).toBe 2
|
||||
buffer = rootView.getActivePaneItem().buffer
|
||||
rootView.getActivePane().splitRight()
|
||||
expect(window.rootView.find('.editor').length).toBe 2
|
||||
|
||||
window.shutdown()
|
||||
|
||||
expect(editor1.getBuffer().subscriptionCount()).toBe 0
|
||||
expect(buffer.subscriptionCount()).toBe 0
|
||||
|
||||
it "only serializes window state the first time it is called", ->
|
||||
deactivateSpy = spyOn(atom, "setRootViewStateForPath").andCallThrough()
|
||||
@@ -129,3 +170,34 @@ describe "Window", ->
|
||||
window.installAtomCommand(commandPath)
|
||||
expect(fs.exists(commandPath)).toBeTruthy()
|
||||
expect(fs.read(commandPath).length).toBeGreaterThan 1
|
||||
|
||||
describe ".deserialize(state)", ->
|
||||
class Foo
|
||||
@deserialize: ({name}) -> new Foo(name)
|
||||
constructor: (@name) ->
|
||||
|
||||
beforeEach ->
|
||||
registerDeserializer(Foo)
|
||||
|
||||
afterEach ->
|
||||
unregisterDeserializer(Foo)
|
||||
|
||||
it "calls deserialize on the deserializer for the given state object, or returns undefined if one can't be found", ->
|
||||
object = deserialize({ deserializer: 'Foo', name: 'Bar' })
|
||||
expect(object.name).toBe 'Bar'
|
||||
expect(deserialize({ deserializer: 'Bogus' })).toBeUndefined()
|
||||
|
||||
describe "when the deserializer has a version", ->
|
||||
beforeEach ->
|
||||
Foo.version = 2
|
||||
|
||||
describe "when the deserialized state has a matching version", ->
|
||||
it "attempts to deserialize the state", ->
|
||||
object = deserialize({ deserializer: 'Foo', version: 2, name: 'Bar' })
|
||||
expect(object.name).toBe 'Bar'
|
||||
|
||||
describe "when the deserialized state has a non-matching version", ->
|
||||
it "returns undefined", ->
|
||||
expect(deserialize({ deserializer: 'Foo', version: 3, name: 'Bar' })).toBeUndefined()
|
||||
expect(deserialize({ deserializer: 'Foo', version: 1, name: 'Bar' })).toBeUndefined()
|
||||
expect(deserialize({ deserializer: 'Foo', name: 'Bar' })).toBeUndefined()
|
||||
|
||||
3
spec/fixtures/markdown/file.markdown
vendored
3
spec/fixtures/markdown/file.markdown
vendored
@@ -0,0 +1,3 @@
|
||||
## File.markdown
|
||||
|
||||
:cool:
|
||||
@@ -1,8 +1,15 @@
|
||||
class Foo
|
||||
registerDeserializer(this)
|
||||
@deserialize: ({data}) -> new Foo(data)
|
||||
constructor: (@data) ->
|
||||
|
||||
module.exports =
|
||||
activateCallCount: 0
|
||||
activationEventCallCount: 0
|
||||
|
||||
activate: ->
|
||||
rootView.getActiveEditor()?.command 'activation-event', =>
|
||||
@activateCallCount++
|
||||
rootView.getActiveView()?.command 'activation-event', =>
|
||||
@activationEventCallCount++
|
||||
|
||||
serialize: ->
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
'activationEvents': ['activation-event']
|
||||
'deferredDeserializers': ['Foo']
|
||||
'main': 'main'
|
||||
|
||||
1
spec/fixtures/sample-with-error.less
vendored
Normal file
1
spec/fixtures/sample-with-error.less
vendored
Normal file
@@ -0,0 +1 @@
|
||||
#header {
|
||||
8
spec/fixtures/sample.less
vendored
Normal file
8
spec/fixtures/sample.less
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
@color: #4D926F;
|
||||
|
||||
#header {
|
||||
color: @color;
|
||||
}
|
||||
h2 {
|
||||
color: @color;
|
||||
}
|
||||
5
spec/fixtures/themes/theme-stylesheet.less
vendored
Normal file
5
spec/fixtures/themes/theme-stylesheet.less
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
@padding: 4321px;
|
||||
|
||||
.editor {
|
||||
padding-top: @padding;
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"stylesheets": ["first.css", "second.css", "last.css"]
|
||||
"stylesheets": ["first.css", "second.less", "last.css"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
.editor {
|
||||
/* padding-top: 102px;*/
|
||||
padding-right: 102px;
|
||||
padding-bottom: 102px;
|
||||
}
|
||||
7
spec/fixtures/themes/theme-with-package-file/second.less
vendored
Normal file
7
spec/fixtures/themes/theme-with-package-file/second.less
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
@number: 102px;
|
||||
|
||||
.editor {
|
||||
/* padding-top: 102px;*/
|
||||
padding-right: @number;
|
||||
padding-bottom: @number;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
.editor {
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
5
spec/fixtures/themes/theme-without-package-file/c.less
vendored
Normal file
5
spec/fixtures/themes/theme-without-package-file/c.less
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
@number: 30px;
|
||||
|
||||
.editor {
|
||||
padding-bottom: @number;
|
||||
}
|
||||
@@ -14,7 +14,8 @@ Editor = require 'editor'
|
||||
TokenizedBuffer = require 'tokenized-buffer'
|
||||
fs = require 'fs'
|
||||
RootView = require 'root-view'
|
||||
requireStylesheet "jasmine.css"
|
||||
Git = require 'git'
|
||||
requireStylesheet "jasmine.less"
|
||||
fixturePackagesPath = require.resolve('fixtures/packages')
|
||||
require.paths.unshift(fixturePackagesPath)
|
||||
keymap.loadBundledKeymaps()
|
||||
@@ -29,8 +30,12 @@ jasmine.getEnv().defaultTimeoutInterval = 5000
|
||||
|
||||
beforeEach ->
|
||||
jQuery.fx.off = true
|
||||
window.fixturesProject = new Project(require.resolve('fixtures'))
|
||||
window.project = fixturesProject
|
||||
window.project = new Project(require.resolve('fixtures'))
|
||||
window.git = Git.open(project.getPath())
|
||||
window.project.on 'path-changed', ->
|
||||
window.git?.destroy()
|
||||
window.git = Git.open(window.project.getPath())
|
||||
|
||||
window.resetTimeouts()
|
||||
atom.atomPackageStates = {}
|
||||
atom.loadedPackages = []
|
||||
@@ -43,13 +48,14 @@ beforeEach ->
|
||||
window.config = new Config()
|
||||
spyOn(config, 'load')
|
||||
spyOn(config, 'save')
|
||||
config.set "editor.fontFamily", "Courier"
|
||||
config.set "editor.fontSize", 16
|
||||
config.set "editor.autoIndent", false
|
||||
config.set "core.disabledPackages", ["package-that-throws-an-exception"]
|
||||
|
||||
# make editor display updates synchronous
|
||||
spyOn(Editor.prototype, 'requestDisplayUpdate').andCallFake -> @updateDisplay()
|
||||
spyOn(RootView.prototype, 'updateWindowTitle').andCallFake ->
|
||||
spyOn(RootView.prototype, 'setTitle').andCallFake (@title) ->
|
||||
spyOn(window, "setTimeout").andCallFake window.fakeSetTimeout
|
||||
spyOn(window, "clearTimeout").andCallFake window.fakeClearTimeout
|
||||
spyOn(File.prototype, "detectResurrectionAfterDelay").andCallFake -> @detectResurrection()
|
||||
@@ -62,25 +68,34 @@ beforeEach ->
|
||||
spyOn($native, 'writeToPasteboard').andCallFake (text) -> pasteboardContent = text
|
||||
spyOn($native, 'readFromPasteboard').andCallFake -> pasteboardContent
|
||||
|
||||
addCustomMatchers(this)
|
||||
|
||||
afterEach ->
|
||||
keymap.bindingSets = bindingSetsToRestore
|
||||
keymap.bindingSetsByFirstKeystrokeToRestore = bindingSetsByFirstKeystrokeToRestore
|
||||
if rootView?
|
||||
rootView.deactivate()
|
||||
rootView.deactivate?()
|
||||
window.rootView = null
|
||||
if project?
|
||||
project.destroy()
|
||||
window.project = null
|
||||
if git?
|
||||
git.destroy()
|
||||
window.git = null
|
||||
$('#jasmine-content').empty()
|
||||
ensureNoPathSubscriptions()
|
||||
atom.pendingModals = [[]]
|
||||
atom.presentingModal = false
|
||||
waits(0) # yield to ui thread to make screen update more frequently
|
||||
|
||||
window.loadPackage = (name, options) ->
|
||||
window.loadPackage = (name, options={}) ->
|
||||
Package = require 'package'
|
||||
packagePath = _.find atom.getPackagePaths(), (packagePath) -> fs.base(packagePath) == name
|
||||
if pack = Package.build(packagePath)
|
||||
pack.load(options)
|
||||
atom.loadedPackages.push(pack)
|
||||
pack.deferActivation = false if options.activateImmediately
|
||||
pack.activate()
|
||||
pack
|
||||
|
||||
# Specs rely on TextMate bundles (but not atom packages)
|
||||
@@ -110,6 +125,23 @@ jasmine.unspy = (object, methodName) ->
|
||||
throw new Error("Not a spy") unless object[methodName].originalValue?
|
||||
object[methodName] = object[methodName].originalValue
|
||||
|
||||
addCustomMatchers = (spec) ->
|
||||
spec.addMatchers
|
||||
toBeInstanceOf: (expected) ->
|
||||
notText = if @isNot then " not" else ""
|
||||
this.message = => "Expected #{jasmine.pp(@actual)} to#{notText} be instance of #{expected.name} class"
|
||||
@actual instanceof expected
|
||||
|
||||
toHaveLength: (expected) ->
|
||||
notText = if @isNot then " not" else ""
|
||||
this.message = => "Expected object with length #{@actual.length} to#{notText} have length #{expected}"
|
||||
@actual.length == expected
|
||||
|
||||
toExistOnDisk: (expected) ->
|
||||
notText = this.isNot and " not" or ""
|
||||
@message = -> return "Expected path '" + @actual + "'" + notText + " to exist."
|
||||
fs.exists(@actual)
|
||||
|
||||
window.keyIdentifierForKey = (key) ->
|
||||
if key.length > 1 # named key
|
||||
key
|
||||
|
||||
@@ -134,11 +134,11 @@ describe 'Child Processes', ->
|
||||
|
||||
waitsForPromise ->
|
||||
options =
|
||||
cwd: fixturesProject.getPath()
|
||||
cwd: "/Applications"
|
||||
stdout: (data) -> output.push(data)
|
||||
stderr: (data) ->
|
||||
|
||||
ChildProcess.exec("pwd", options)
|
||||
|
||||
runs ->
|
||||
expect(output.join('')).toBe "#{fixturesProject.getPath()}\n"
|
||||
expect(output.join('')).toBe "/Applications\n"
|
||||
|
||||
@@ -62,10 +62,14 @@ describe "CSON", ->
|
||||
it "formats the undefined value as null", ->
|
||||
expect(CSON.stringify(['a', undefined, 'b'])).toBe "[\n 'a'\n null\n 'b'\n]"
|
||||
|
||||
describe "when the array contains an object", ->
|
||||
it "wraps the object in {}", ->
|
||||
expect(CSON.stringify([{a:'b', a1: 'b1'}, {c: 'd'}])).toBe "[\n {\n 'a': 'b'\n 'a1': 'b1'\n }\n {\n 'c': 'd'\n }\n]"
|
||||
|
||||
describe "when formatting an object", ->
|
||||
describe "when the object is empty", ->
|
||||
it "returns the empty string", ->
|
||||
expect(CSON.stringify({})).toBe ""
|
||||
it "returns {}", ->
|
||||
expect(CSON.stringify({})).toBe "{}"
|
||||
|
||||
it "returns formatted CSON", ->
|
||||
expect(CSON.stringify(a: {b: 'c'})).toBe "'a':\n 'b': 'c'"
|
||||
|
||||
@@ -86,7 +86,7 @@ describe "fs", ->
|
||||
it "calls fn for every path in the tree at the given path", ->
|
||||
paths = []
|
||||
onPath = (path) ->
|
||||
paths.push(fs.join(fixturesDir, path))
|
||||
paths.push(path)
|
||||
true
|
||||
fs.traverseTree fixturesDir, onPath, onPath
|
||||
expect(paths).toEqual fs.listTree(fixturesDir)
|
||||
@@ -106,14 +106,16 @@ describe "fs", ->
|
||||
expect(path).not.toMatch /\/dir\//
|
||||
|
||||
it "returns entries if path is a symlink", ->
|
||||
symlinkPath = fs.join(fixturesDir, 'symlink-to-dir')
|
||||
symlinkPaths = []
|
||||
onSymlinkPath = (path) -> symlinkPaths.push(path)
|
||||
onSymlinkPath = (path) -> symlinkPaths.push(path.substring(symlinkPath.length + 1))
|
||||
|
||||
regularPath = fs.join(fixturesDir, 'dir')
|
||||
paths = []
|
||||
onPath = (path) -> paths.push(path)
|
||||
onPath = (path) -> paths.push(path.substring(regularPath.length + 1))
|
||||
|
||||
fs.traverseTree(fs.join(fixturesDir, 'symlink-to-dir'), onSymlinkPath, onSymlinkPath)
|
||||
fs.traverseTree(fs.join(fixturesDir, 'dir'), onPath, onPath)
|
||||
fs.traverseTree(symlinkPath, onSymlinkPath, onSymlinkPath)
|
||||
fs.traverseTree(regularPath, onPath, onPath)
|
||||
|
||||
expect(symlinkPaths).toEqual(paths)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
{View, $$} = require 'space-pen'
|
||||
|
||||
describe 'jQuery extensions', ->
|
||||
@@ -41,7 +42,7 @@ describe 'jQuery extensions', ->
|
||||
element.trigger 'foo'
|
||||
expect(events).toEqual [2,1,3]
|
||||
|
||||
describe "$.fn.events() and $.fn.document", ->
|
||||
describe "$.fn.events() and $.fn.document(...)", ->
|
||||
it "returns a list of all events being listened for on the target node or its ancestors, along with their documentation string", ->
|
||||
view = $$ ->
|
||||
@div id: 'a', =>
|
||||
@@ -49,20 +50,18 @@ describe 'jQuery extensions', ->
|
||||
@div id: 'c'
|
||||
@div id: 'd'
|
||||
|
||||
view.document
|
||||
'a1': "This is event A2"
|
||||
'b2': "This is event b2"
|
||||
view.document 'a1', "This is event A2"
|
||||
view.document 'b2', "This is event b2"
|
||||
|
||||
view.document 'a1': "A1: Waste perfectly-good steak"
|
||||
view.document 'a1', "A1: Waste perfectly-good steak"
|
||||
view.on 'a1', ->
|
||||
view.on 'a2', ->
|
||||
view.on 'b1', -> # should not appear as a duplicate
|
||||
|
||||
divB = view.find('#b')
|
||||
|
||||
divB.document
|
||||
'b1': "B1: Super-sonic bomber"
|
||||
'b2': "B2: Looks evil. Kinda is."
|
||||
divB.document 'b1', "B1: Super-sonic bomber"
|
||||
divB.document 'b2', "B2: Looks evil. Kinda is."
|
||||
divB.on 'b1', ->
|
||||
divB.on 'b2', ->
|
||||
|
||||
@@ -76,6 +75,80 @@ describe 'jQuery extensions', ->
|
||||
'a1': "A1: Waste perfectly-good steak"
|
||||
'a2': null
|
||||
|
||||
describe "$.fn.command(eventName, [selector, options,] handler)", ->
|
||||
[view, handler] = []
|
||||
|
||||
beforeEach ->
|
||||
view = $$ ->
|
||||
@div class: 'a', =>
|
||||
@div class: 'b'
|
||||
@div class: 'c'
|
||||
handler = jasmine.createSpy("commandHandler")
|
||||
|
||||
it "binds the handler to the given event / selector for all argument combinations", ->
|
||||
view.command 'test:foo', handler
|
||||
view.trigger 'test:foo'
|
||||
expect(handler).toHaveBeenCalled()
|
||||
handler.reset()
|
||||
|
||||
view.command 'test:bar', '.b', handler
|
||||
view.find('.b').trigger 'test:bar'
|
||||
view.find('.c').trigger 'test:bar'
|
||||
expect(handler.callCount).toBe 1
|
||||
handler.reset()
|
||||
|
||||
view.command 'test:baz', doc: 'Spaz', handler
|
||||
view.trigger 'test:baz'
|
||||
expect(handler).toHaveBeenCalled()
|
||||
handler.reset()
|
||||
|
||||
view.command 'test:quux', '.c', doc: 'Lorem', handler
|
||||
view.find('.b').trigger 'test:quux'
|
||||
view.find('.c').trigger 'test:quux'
|
||||
expect(handler.callCount).toBe 1
|
||||
|
||||
it "passes the 'data' option through when binding the event handler", ->
|
||||
view.command 'test:foo', data: "bar", handler
|
||||
view.trigger 'test:foo'
|
||||
expect(handler.argsForCall[0][0].data).toBe 'bar'
|
||||
|
||||
it "sets a custom docstring if the 'doc' option is specified", ->
|
||||
view.command 'test:foo', doc: "Foo!", handler
|
||||
expect(view.events()).toEqual 'test:foo': 'Test: Foo!'
|
||||
|
||||
it "capitalizes the 'github' prefix how we like it", ->
|
||||
view.command 'github:spelling', handler
|
||||
expect(view.events()).toEqual 'github:spelling': 'GitHub: Spelling'
|
||||
|
||||
describe "$.fn.scrollUp/Down/ToTop/ToBottom", ->
|
||||
it "scrolls the element in the specified way if possible", ->
|
||||
view = $$ -> @div => _.times 20, => @div('A')
|
||||
view.css(height: 100, width: 100, overflow: 'scroll')
|
||||
view.attachToDom()
|
||||
|
||||
view.scrollUp()
|
||||
expect(view.scrollTop()).toBe 0
|
||||
|
||||
view.scrollDown()
|
||||
expect(view.scrollTop()).toBeGreaterThan 0
|
||||
previousScrollTop = view.scrollTop()
|
||||
view.scrollDown()
|
||||
expect(view.scrollTop()).toBeGreaterThan previousScrollTop
|
||||
|
||||
view.scrollToBottom()
|
||||
expect(view.scrollTop()).toBe view.prop('scrollHeight') - 100
|
||||
previousScrollTop = view.scrollTop()
|
||||
view.scrollDown()
|
||||
expect(view.scrollTop()).toBe previousScrollTop
|
||||
view.scrollUp()
|
||||
expect(view.scrollTop()).toBeLessThan previousScrollTop
|
||||
previousScrollTop = view.scrollTop()
|
||||
view.scrollUp()
|
||||
expect(view.scrollTop()).toBeLessThan previousScrollTop
|
||||
|
||||
view.scrollToTop()
|
||||
expect(view.scrollTop()).toBe 0
|
||||
|
||||
describe "Event.prototype", ->
|
||||
class GrandchildView extends View
|
||||
@content: -> @div class: 'grandchild'
|
||||
|
||||
@@ -6,67 +6,22 @@ $ = require 'jquery'
|
||||
module.exports =
|
||||
class AtomPackage extends Package
|
||||
metadata: null
|
||||
packageMain: null
|
||||
mainModule: null
|
||||
deferActivation: false
|
||||
|
||||
load: ({activateImmediately}={}) ->
|
||||
load: ->
|
||||
try
|
||||
@loadMetadata()
|
||||
@loadKeymaps()
|
||||
@loadStylesheets()
|
||||
if @metadata.activationEvents and not activateImmediately
|
||||
@subscribeToActivationEvents()
|
||||
if @deferActivation = @metadata.activationEvents?
|
||||
@registerDeferredDeserializers()
|
||||
else
|
||||
@activatePackageMain()
|
||||
@requireMainModule()
|
||||
catch e
|
||||
console.warn "Failed to load package named '#{@name}'", e.stack
|
||||
this
|
||||
|
||||
disableEventHandlersOnBubblePath: (event) ->
|
||||
bubblePathEventHandlers = []
|
||||
disabledHandler = ->
|
||||
element = $(event.target)
|
||||
while element.length
|
||||
if eventHandlers = element.data('events')?[event.type]
|
||||
for eventHandler in eventHandlers
|
||||
eventHandler.disabledHandler = eventHandler.handler
|
||||
eventHandler.handler = disabledHandler
|
||||
bubblePathEventHandlers.push(eventHandler)
|
||||
element = element.parent()
|
||||
bubblePathEventHandlers
|
||||
|
||||
restoreEventHandlersOnBubblePath: (eventHandlers) ->
|
||||
for eventHandler in eventHandlers
|
||||
eventHandler.handler = eventHandler.disabledHandler
|
||||
delete eventHandler.disabledHandler
|
||||
|
||||
unsubscribeFromActivationEvents: (activateHandler) ->
|
||||
if _.isArray(@metadata.activationEvents)
|
||||
rootView.off(event, activateHandler) for event in @metadata.activationEvents
|
||||
else
|
||||
rootView.off(event, selector, activateHandler) for event, selector of @metadata.activationEvents
|
||||
|
||||
subscribeToActivationEvents: () ->
|
||||
activateHandler = (event) =>
|
||||
bubblePathEventHandlers = @disableEventHandlersOnBubblePath(event)
|
||||
@activatePackageMain()
|
||||
$(event.target).trigger(event)
|
||||
@restoreEventHandlersOnBubblePath(bubblePathEventHandlers)
|
||||
@unsubscribeFromActivationEvents(activateHandler)
|
||||
|
||||
if _.isArray(@metadata.activationEvents)
|
||||
rootView.command(event, activateHandler) for event in @metadata.activationEvents
|
||||
else
|
||||
rootView.command(event, selector, activateHandler) for event, selector of @metadata.activationEvents
|
||||
|
||||
activatePackageMain: ->
|
||||
mainPath = @path
|
||||
mainPath = fs.join(mainPath, @metadata.main) if @metadata.main
|
||||
mainPath = require.resolve(mainPath)
|
||||
if fs.isFile(mainPath)
|
||||
@packageMain = require(mainPath)
|
||||
config.setDefaults(@name, @packageMain.configDefaults)
|
||||
atom.activateAtomPackage(this)
|
||||
|
||||
loadMetadata: ->
|
||||
if metadataPath = fs.resolveExtension(fs.join(@path, 'package'), ['cson', 'json'])
|
||||
@metadata = fs.readObject(metadataPath)
|
||||
@@ -86,3 +41,65 @@ class AtomPackage extends Package
|
||||
stylesheetDirPath = fs.join(@path, 'stylesheets')
|
||||
for stylesheetPath in fs.list(stylesheetDirPath)
|
||||
requireStylesheet(stylesheetPath)
|
||||
|
||||
activate: ->
|
||||
if @deferActivation
|
||||
@subscribeToActivationEvents()
|
||||
else
|
||||
try
|
||||
if @requireMainModule()
|
||||
config.setDefaults(@name, @mainModule.configDefaults)
|
||||
atom.activateAtomPackage(this)
|
||||
catch e
|
||||
console.warn "Failed to activate package named '#{@name}'", e.stack
|
||||
|
||||
requireMainModule: ->
|
||||
return @mainModule if @mainModule
|
||||
mainPath = @path
|
||||
mainPath = fs.join(mainPath, @metadata.main) if @metadata.main
|
||||
mainPath = require.resolve(mainPath)
|
||||
@mainModule = require(mainPath) if fs.isFile(mainPath)
|
||||
|
||||
registerDeferredDeserializers: ->
|
||||
for deserializerName in @metadata.deferredDeserializers ? []
|
||||
registerDeferredDeserializer deserializerName, => @requireMainModule()
|
||||
|
||||
subscribeToActivationEvents: () ->
|
||||
return unless @metadata.activationEvents?
|
||||
|
||||
activateHandler = (event) =>
|
||||
bubblePathEventHandlers = @disableEventHandlersOnBubblePath(event)
|
||||
@deferActivation = false
|
||||
@activate()
|
||||
$(event.target).trigger(event)
|
||||
@restoreEventHandlersOnBubblePath(bubblePathEventHandlers)
|
||||
@unsubscribeFromActivationEvents(activateHandler)
|
||||
|
||||
if _.isArray(@metadata.activationEvents)
|
||||
rootView.command(event, activateHandler) for event in @metadata.activationEvents
|
||||
else
|
||||
rootView.command(event, selector, activateHandler) for event, selector of @metadata.activationEvents
|
||||
|
||||
unsubscribeFromActivationEvents: (activateHandler) ->
|
||||
if _.isArray(@metadata.activationEvents)
|
||||
rootView.off(event, activateHandler) for event in @metadata.activationEvents
|
||||
else
|
||||
rootView.off(event, selector, activateHandler) for event, selector of @metadata.activationEvents
|
||||
|
||||
disableEventHandlersOnBubblePath: (event) ->
|
||||
bubblePathEventHandlers = []
|
||||
disabledHandler = ->
|
||||
element = $(event.target)
|
||||
while element.length
|
||||
if eventHandlers = element.data('events')?[event.type]
|
||||
for eventHandler in eventHandlers
|
||||
eventHandler.disabledHandler = eventHandler.handler
|
||||
eventHandler.handler = disabledHandler
|
||||
bubblePathEventHandlers.push(eventHandler)
|
||||
element = element.parent()
|
||||
bubblePathEventHandlers
|
||||
|
||||
restoreEventHandlersOnBubblePath: (eventHandlers) ->
|
||||
for eventHandler in eventHandlers
|
||||
eventHandler.handler = eventHandler.disabledHandler
|
||||
delete eventHandler.disabledHandler
|
||||
|
||||
@@ -5,10 +5,10 @@ module.exports =
|
||||
class AtomTheme extends Theme
|
||||
|
||||
loadStylesheet: (stylesheetPath)->
|
||||
@stylesheets[stylesheetPath] = fs.read(stylesheetPath)
|
||||
@stylesheets[stylesheetPath] = window.loadStylesheet(stylesheetPath)
|
||||
|
||||
load: ->
|
||||
if fs.extension(@path) is '.css'
|
||||
if fs.extension(@path) in ['.css', '.less']
|
||||
@loadStylesheet(@path)
|
||||
else
|
||||
metadataPath = fs.resolveExtension(fs.join(@path, 'package'), ['cson', 'json'])
|
||||
@@ -17,6 +17,6 @@ class AtomTheme extends Theme
|
||||
if stylesheetNames
|
||||
@loadStylesheet(fs.join(@path, name)) for name in stylesheetNames
|
||||
else
|
||||
@loadStylesheet(stylesheetPath) for stylesheetPath in fs.list(@path, ['.css'])
|
||||
@loadStylesheet(stylesheetPath) for stylesheetPath in fs.list(@path, ['.css', '.less'])
|
||||
|
||||
super
|
||||
|
||||
@@ -10,21 +10,24 @@ originalSendMessageToBrowserProcess = atom.sendMessageToBrowserProcess
|
||||
|
||||
_.extend atom,
|
||||
exitWhenDone: window.location.params.exitWhenDone
|
||||
devMode: window.location.params.devMode
|
||||
loadedThemes: []
|
||||
pendingBrowserProcessCallbacks: {}
|
||||
loadedPackages: []
|
||||
activatedAtomPackages: []
|
||||
atomPackageStates: {}
|
||||
presentingModal: false
|
||||
pendingModals: [[]]
|
||||
|
||||
getPathToOpen: ->
|
||||
@getWindowState('pathToOpen') ? window.location.params.pathToOpen
|
||||
|
||||
activateAtomPackage: (pack) ->
|
||||
@activatedAtomPackages.push(pack)
|
||||
pack.packageMain.activate(@atomPackageStates[pack.name] ? {})
|
||||
pack.mainModule.activate(@atomPackageStates[pack.name] ? {})
|
||||
|
||||
deactivateAtomPackages: ->
|
||||
pack.packageMain.deactivate?() for pack in @activatedAtomPackages
|
||||
pack.mainModule.deactivate?() for pack in @activatedAtomPackages
|
||||
@activatedAtomPackages = []
|
||||
|
||||
serializeAtomPackages: ->
|
||||
@@ -32,7 +35,7 @@ _.extend atom,
|
||||
for pack in @loadedPackages
|
||||
if pack in @activatedAtomPackages
|
||||
try
|
||||
packageStates[pack.name] = pack.packageMain.serialize?()
|
||||
packageStates[pack.name] = pack.mainModule.serialize?()
|
||||
catch e
|
||||
console?.error("Exception serializing '#{pack.name}' package's module\n", e.stack)
|
||||
else
|
||||
@@ -58,6 +61,9 @@ _.extend atom,
|
||||
|
||||
new LoadTextMatePackagesTask(textMatePackages).start() if textMatePackages.length > 0
|
||||
|
||||
activatePackages: ->
|
||||
pack.activate() for pack in @loadedPackages
|
||||
|
||||
getLoadedPackages: ->
|
||||
_.clone(@loadedPackages)
|
||||
|
||||
@@ -101,15 +107,50 @@ _.extend atom,
|
||||
@sendMessageToBrowserProcess('newWindow', args)
|
||||
|
||||
confirm: (message, detailedMessage, buttonLabelsAndCallbacks...) ->
|
||||
args = [message, detailedMessage]
|
||||
callbacks = []
|
||||
while buttonLabelsAndCallbacks.length
|
||||
args.push(buttonLabelsAndCallbacks.shift())
|
||||
callbacks.push(buttonLabelsAndCallbacks.shift())
|
||||
@sendMessageToBrowserProcess('confirm', args, callbacks)
|
||||
wrapCallback = (callback) => => @dismissModal(callback)
|
||||
@presentModal =>
|
||||
args = [message, detailedMessage]
|
||||
callbacks = []
|
||||
while buttonLabelsAndCallbacks.length
|
||||
do =>
|
||||
buttonLabel = buttonLabelsAndCallbacks.shift()
|
||||
buttonCallback = buttonLabelsAndCallbacks.shift()
|
||||
args.push(buttonLabel)
|
||||
callbacks.push(=> @dismissModal(buttonCallback))
|
||||
@sendMessageToBrowserProcess('confirm', args, callbacks)
|
||||
|
||||
showSaveDialog: (callback) ->
|
||||
@sendMessageToBrowserProcess('showSaveDialog', [], callback)
|
||||
@presentModal =>
|
||||
@sendMessageToBrowserProcess('showSaveDialog', [], (path) => @dismissModal(callback, path))
|
||||
|
||||
presentModal: (fn) ->
|
||||
if @presentingModal
|
||||
@pushPendingModal(fn)
|
||||
else
|
||||
@presentingModal = true
|
||||
fn()
|
||||
|
||||
dismissModal: (fn, args...) ->
|
||||
@pendingModals.push([]) # prioritize any modals presented during dismiss callback
|
||||
fn?(args...)
|
||||
@presentingModal = false
|
||||
if fn = @shiftPendingModal()
|
||||
_.delay (=> @presentModal(fn)), 50 # let view update before next dialog
|
||||
|
||||
pushPendingModal: (fn) ->
|
||||
# pendingModals is a stack of queues. enqueue to top of stack.
|
||||
stackSize = @pendingModals.length
|
||||
@pendingModals[stackSize - 1].push(fn)
|
||||
|
||||
shiftPendingModal: ->
|
||||
# pop pendingModals stack if its top queue is empty, otherwise shift off the topmost queue
|
||||
stackSize = @pendingModals.length
|
||||
currentQueueSize = @pendingModals[stackSize - 1].length
|
||||
if stackSize > 1 and currentQueueSize == 0
|
||||
@pendingModals.pop()
|
||||
@shiftPendingModal()
|
||||
else
|
||||
@pendingModals[stackSize - 1].shift()
|
||||
|
||||
toggleDevTools: ->
|
||||
@sendMessageToBrowserProcess('toggleDevTools')
|
||||
|
||||
@@ -73,7 +73,7 @@ class BufferChangeOperation
|
||||
event = { oldRange, newRange, oldText, newText }
|
||||
@updateMarkers(event)
|
||||
@buffer.trigger 'changed', event
|
||||
@buffer.scheduleStoppedChangingEvent()
|
||||
@buffer.scheduleModifiedEvents()
|
||||
@resumeMarkerObservation()
|
||||
@buffer.trigger 'markers-updated'
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ class Buffer
|
||||
|
||||
@file.on "removed", =>
|
||||
@updateCachedDiskContents()
|
||||
@trigger "contents-modified", {differsFromDisk: true}
|
||||
@triggerModifiedStatusChanged(@isModified())
|
||||
|
||||
@file.on "moved", =>
|
||||
@trigger "path-changed", this
|
||||
@@ -78,6 +78,7 @@ class Buffer
|
||||
@trigger 'will-reload'
|
||||
@updateCachedDiskContents()
|
||||
@setText(@cachedDiskContents)
|
||||
@triggerModifiedStatusChanged(false)
|
||||
@trigger 'reloaded'
|
||||
|
||||
updateCachedDiskContents: ->
|
||||
@@ -252,6 +253,7 @@ class Buffer
|
||||
@setPath(path)
|
||||
@cachedDiskContents = @getText()
|
||||
@file.write(@getText())
|
||||
@triggerModifiedStatusChanged(false)
|
||||
@trigger 'saved'
|
||||
|
||||
isModified: ->
|
||||
@@ -419,21 +421,25 @@ class Buffer
|
||||
return match[0][0] != '\t'
|
||||
undefined
|
||||
|
||||
getRepo: -> @project?.repo
|
||||
|
||||
checkoutHead: ->
|
||||
path = @getPath()
|
||||
return unless path
|
||||
if @getRepo()?.checkoutHead(path)
|
||||
@trigger 'git-status-changed'
|
||||
git?.checkoutHead(path)
|
||||
|
||||
scheduleStoppedChangingEvent: ->
|
||||
scheduleModifiedEvents: ->
|
||||
clearTimeout(@stoppedChangingTimeout) if @stoppedChangingTimeout
|
||||
stoppedChangingCallback = =>
|
||||
@stoppedChangingTimeout = null
|
||||
@trigger 'contents-modified', {differsFromDisk: @isModified()}
|
||||
modifiedStatus = @isModified()
|
||||
@trigger 'contents-modified', modifiedStatus
|
||||
@triggerModifiedStatusChanged(modifiedStatus)
|
||||
@stoppedChangingTimeout = setTimeout(stoppedChangingCallback, @stoppedChangingDelay)
|
||||
|
||||
triggerModifiedStatusChanged: (modifiedStatus) ->
|
||||
return if modifiedStatus is @previousModifiedStatus
|
||||
@previousModifiedStatus = modifiedStatus
|
||||
@trigger 'modified-status-changed', modifiedStatus
|
||||
|
||||
fileExists: ->
|
||||
@file.exists()
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ class Config
|
||||
userPackagesDirPath: userPackagesDirPath
|
||||
defaultSettings: null
|
||||
settings: null
|
||||
configFileHasErrors: null
|
||||
|
||||
constructor: ->
|
||||
@defaultSettings =
|
||||
@@ -37,16 +38,16 @@ class Config
|
||||
templateConfigDirPath = fs.resolve(window.resourcePath, 'dot-atom')
|
||||
|
||||
onConfigDirFile = (path) =>
|
||||
templatePath = fs.join(templateConfigDirPath, path)
|
||||
configPath = fs.join(@configDirPath, path)
|
||||
fs.write(configPath, fs.read(templatePath))
|
||||
relativePath = path.substring(templateConfigDirPath.length + 1)
|
||||
configPath = fs.join(@configDirPath, relativePath)
|
||||
fs.write(configPath, fs.read(path))
|
||||
fs.traverseTree(templateConfigDirPath, onConfigDirFile, (path) -> true)
|
||||
|
||||
configThemeDirPath = fs.join(@configDirPath, 'themes')
|
||||
onThemeDirFile = (path) ->
|
||||
templatePath = fs.join(bundledThemesDirPath, path)
|
||||
configPath = fs.join(configThemeDirPath, path)
|
||||
fs.write(configPath, fs.read(templatePath))
|
||||
relativePath = path.substring(bundledThemesDirPath.length + 1)
|
||||
configPath = fs.join(configThemeDirPath, relativePath)
|
||||
fs.write(configPath, fs.read(path))
|
||||
fs.traverseTree(bundledThemesDirPath, onThemeDirFile, (path) -> true)
|
||||
|
||||
load: ->
|
||||
@@ -55,8 +56,13 @@ class Config
|
||||
|
||||
loadUserConfig: ->
|
||||
if fs.exists(@configFilePath)
|
||||
userConfig = fs.readObject(@configFilePath)
|
||||
_.extend(@settings, userConfig)
|
||||
try
|
||||
userConfig = fs.readObject(@configFilePath)
|
||||
_.extend(@settings, userConfig)
|
||||
catch e
|
||||
@configFileHasErrors = true
|
||||
console.error "Failed to load user config '#{@configFilePath}'", e.message
|
||||
console.error e.stack
|
||||
|
||||
get: (keyPath) ->
|
||||
_.valueForKeyPath(@settings, keyPath) ?
|
||||
@@ -92,6 +98,7 @@ class Config
|
||||
subscription
|
||||
|
||||
update: ->
|
||||
return if @configFileHasErrors
|
||||
@save()
|
||||
@trigger 'updated'
|
||||
|
||||
|
||||
@@ -14,17 +14,22 @@ module.exports =
|
||||
class EditSession
|
||||
registerDeserializer(this)
|
||||
|
||||
@deserialize: (state, project) ->
|
||||
@deserialize: (state) ->
|
||||
if fs.exists(state.buffer)
|
||||
session = project.buildEditSessionForPath(state.buffer)
|
||||
session = project.buildEditSession(state.buffer)
|
||||
else
|
||||
console.warn "Could not build edit session for path '#{state.buffer}' because that file no longer exists" if state.buffer
|
||||
session = project.buildEditSessionForPath(null)
|
||||
session = project.buildEditSession(null)
|
||||
session.setScrollTop(state.scrollTop)
|
||||
session.setScrollLeft(state.scrollLeft)
|
||||
session.setCursorScreenPosition(state.cursorScreenPosition)
|
||||
session
|
||||
|
||||
@identifiedBy: 'path'
|
||||
|
||||
@deserializesToSameObject: (state, editSession) ->
|
||||
state.path
|
||||
|
||||
scrollTop: 0
|
||||
scrollLeft: 0
|
||||
languageMode: null
|
||||
@@ -43,17 +48,40 @@ class EditSession
|
||||
@addCursorAtScreenPosition([0, 0])
|
||||
|
||||
@buffer.retain()
|
||||
@subscribe @buffer, "path-changed", => @trigger "path-changed"
|
||||
@subscribe @buffer, "path-changed", =>
|
||||
@project.setPath(fs.directory(@getPath())) unless @project.getPath()?
|
||||
@trigger "title-changed"
|
||||
@trigger "path-changed"
|
||||
@subscribe @buffer, "contents-conflicted", => @trigger "contents-conflicted"
|
||||
@subscribe @buffer, "markers-updated", => @mergeCursors()
|
||||
@subscribe @buffer, "modified-status-changed", => @trigger "modified-status-changed"
|
||||
|
||||
@preserveCursorPositionOnBufferReload()
|
||||
|
||||
@subscribe @displayBuffer, "changed", (e) =>
|
||||
@trigger 'screen-lines-changed', e
|
||||
|
||||
@subscribe syntax, 'grammars-loaded', => @reloadGrammar()
|
||||
|
||||
getViewClass: ->
|
||||
require 'editor'
|
||||
|
||||
getTitle: ->
|
||||
if path = @getPath()
|
||||
fs.base(path)
|
||||
else
|
||||
'untitled'
|
||||
|
||||
getLongTitle: ->
|
||||
if path = @getPath()
|
||||
fileName = fs.base(path)
|
||||
directory = fs.base(fs.directory(path))
|
||||
"#{fileName} - #{directory}"
|
||||
else
|
||||
'untitled'
|
||||
|
||||
destroy: ->
|
||||
throw new Error("Edit session already destroyed") if @destroyed
|
||||
return if @destroyed
|
||||
@destroyed = true
|
||||
@unsubscribe()
|
||||
@buffer.release()
|
||||
@@ -130,6 +158,7 @@ class EditSession
|
||||
saveAs: (path) -> @buffer.saveAs(path)
|
||||
getFileExtension: -> @buffer.getExtension()
|
||||
getPath: -> @buffer.getPath()
|
||||
getUri: -> @getPath()
|
||||
isBufferRowBlank: (bufferRow) -> @buffer.isRowBlank(bufferRow)
|
||||
nextNonBlankBufferRow: (bufferRow) -> @buffer.nextNonBlankRow(bufferRow)
|
||||
getEofBufferPosition: -> @buffer.getEofPosition()
|
||||
@@ -814,11 +843,10 @@ class EditSession
|
||||
getGrammar: -> @languageMode.grammar
|
||||
|
||||
reloadGrammar: ->
|
||||
grammarChanged = @languageMode.reloadGrammar()
|
||||
if grammarChanged
|
||||
if @languageMode.reloadGrammar()
|
||||
@unfoldAll()
|
||||
@displayBuffer.tokenizedBuffer.resetScreenLines()
|
||||
grammarChanged
|
||||
true
|
||||
|
||||
getDebugSnapshot: ->
|
||||
[
|
||||
|
||||
@@ -16,7 +16,6 @@ class Editor extends View
|
||||
fontSize: 20
|
||||
showInvisibles: false
|
||||
showIndentGuide: false
|
||||
autosave: false
|
||||
autoIndent: true
|
||||
autoIndentOnPaste: false
|
||||
nonWordCharacters: "./\\()\"':,.;<>~!@#$%^&*|+=[]{}`~?-"
|
||||
@@ -49,8 +48,6 @@ class Editor extends View
|
||||
lineCache: null
|
||||
isFocused: false
|
||||
activeEditSession: null
|
||||
closedEditSessions: null
|
||||
editSessions: null
|
||||
attached: false
|
||||
lineOverdraw: 10
|
||||
pendingChanges: null
|
||||
@@ -58,16 +55,13 @@ class Editor extends View
|
||||
newSelections: null
|
||||
redrawOnReattach: false
|
||||
|
||||
@deserialize: (state) ->
|
||||
editor = new Editor(mini: state.mini, deserializing: true)
|
||||
editSessions = state.editSessions.map (state) -> EditSession.deserialize(state, project)
|
||||
editor.pushEditSession(editSession) for editSession in editSessions
|
||||
editor.setActiveEditSessionIndex(state.activeEditSessionIndex)
|
||||
editor.isFocused = state.isFocused
|
||||
editor
|
||||
initialize: (editSessionOrOptions) ->
|
||||
if editSessionOrOptions instanceof EditSession
|
||||
editSession = editSessionOrOptions
|
||||
else
|
||||
{editSession, @mini} = (editSessionOrOptions ? {})
|
||||
|
||||
initialize: ({editSession, @mini, deserializing} = {}) ->
|
||||
requireStylesheet 'editor.css'
|
||||
requireStylesheet 'editor.less'
|
||||
|
||||
@id = Editor.nextEditorId++
|
||||
@lineCache = []
|
||||
@@ -76,8 +70,6 @@ class Editor extends View
|
||||
@handleEvents()
|
||||
@cursorViews = []
|
||||
@selectionViews = []
|
||||
@editSessions = []
|
||||
@closedEditSessions = []
|
||||
@pendingChanges = []
|
||||
@newCursors = []
|
||||
@newSelections = []
|
||||
@@ -91,18 +83,8 @@ class Editor extends View
|
||||
tabLength: 2
|
||||
softTabs: true
|
||||
)
|
||||
else if not deserializing
|
||||
throw new Error("Editor must be constructed with an 'editSession' or 'mini: true' param")
|
||||
|
||||
serialize: ->
|
||||
@saveScrollPositionForActiveEditSession()
|
||||
deserializer: "Editor"
|
||||
editSessions: @editSessions.map (session) -> session.serialize()
|
||||
activeEditSessionIndex: @getActiveEditSessionIndex()
|
||||
isFocused: @isFocused
|
||||
|
||||
copy: ->
|
||||
Editor.deserialize(@serialize(), rootView)
|
||||
else
|
||||
throw new Error("Must supply an EditSession or mini: true")
|
||||
|
||||
bindKeys: ->
|
||||
editorBindings =
|
||||
@@ -155,9 +137,6 @@ class Editor extends View
|
||||
'core:select-down': @selectDown
|
||||
'core:select-to-top': @selectToTop
|
||||
'core:select-to-bottom': @selectToBottom
|
||||
'core:close': @destroyActiveEditSession
|
||||
'editor:save': @save
|
||||
'editor:save-as': @saveAs
|
||||
'editor:newline-below': @insertNewlineBelow
|
||||
'editor:newline-above': @insertNewlineAbove
|
||||
'editor:toggle-soft-tabs': @toggleSoftTabs
|
||||
@@ -167,32 +146,14 @@ class Editor extends View
|
||||
'editor:fold-current-row': @foldCurrentRow
|
||||
'editor:unfold-current-row': @unfoldCurrentRow
|
||||
'editor:fold-selection': @foldSelection
|
||||
'editor:split-left': @splitLeft
|
||||
'editor:split-right': @splitRight
|
||||
'editor:split-up': @splitUp
|
||||
'editor:split-down': @splitDown
|
||||
'editor:show-next-buffer': @loadNextEditSession
|
||||
'editor:show-buffer-1': => @setActiveEditSessionIndex(0) if @editSessions[0]
|
||||
'editor:show-buffer-2': => @setActiveEditSessionIndex(1) if @editSessions[1]
|
||||
'editor:show-buffer-3': => @setActiveEditSessionIndex(2) if @editSessions[2]
|
||||
'editor:show-buffer-4': => @setActiveEditSessionIndex(3) if @editSessions[3]
|
||||
'editor:show-buffer-5': => @setActiveEditSessionIndex(4) if @editSessions[4]
|
||||
'editor:show-buffer-6': => @setActiveEditSessionIndex(5) if @editSessions[5]
|
||||
'editor:show-buffer-7': => @setActiveEditSessionIndex(6) if @editSessions[6]
|
||||
'editor:show-buffer-8': => @setActiveEditSessionIndex(7) if @editSessions[7]
|
||||
'editor:show-buffer-9': => @setActiveEditSessionIndex(8) if @editSessions[8]
|
||||
'editor:show-previous-buffer': @loadPreviousEditSession
|
||||
'editor:toggle-line-comments': @toggleLineCommentsInSelection
|
||||
'editor:log-cursor-scope': @logCursorScope
|
||||
'editor:checkout-head-revision': @checkoutHead
|
||||
'editor:close-other-edit-sessions': @destroyInactiveEditSessions
|
||||
'editor:close-all-edit-sessions': @destroyAllEditSessions
|
||||
'editor:select-grammar': @selectGrammar
|
||||
'editor:copy-path': @copyPathToPasteboard
|
||||
'editor:move-line-up': @moveLineUp
|
||||
'editor:move-line-down': @moveLineDown
|
||||
'editor:duplicate-line': @duplicateLine
|
||||
'editor:undo-close-session': @undoDestroySession
|
||||
'editor:toggle-indent-guide': => config.set('editor.showIndentGuide', !config.get('editor.showIndentGuide'))
|
||||
'editor:save-debug-snapshot': @saveDebugSnapshot
|
||||
|
||||
@@ -343,7 +304,7 @@ class Editor extends View
|
||||
checkoutHead: -> @getBuffer().checkoutHead()
|
||||
setText: (text) -> @getBuffer().setText(text)
|
||||
getText: -> @getBuffer().getText()
|
||||
getPath: -> @getBuffer().getPath()
|
||||
getPath: -> @activeEditSession?.getPath()
|
||||
getLineCount: -> @getBuffer().getLineCount()
|
||||
getLastBufferRow: -> @getBuffer().getLastRow()
|
||||
getTextInRange: (range) -> @getBuffer().getTextInRange(range)
|
||||
@@ -367,13 +328,11 @@ class Editor extends View
|
||||
false
|
||||
|
||||
@hiddenInput.on 'focus', =>
|
||||
rootView?.editorFocused(this)
|
||||
@isFocused = true
|
||||
@addClass 'is-focused'
|
||||
|
||||
@hiddenInput.on 'focusout', =>
|
||||
@isFocused = false
|
||||
@autosave() if config.get "editor.autosave"
|
||||
@removeClass 'is-focused'
|
||||
|
||||
@underlayer.on 'click', (e) =>
|
||||
@@ -436,10 +395,7 @@ class Editor extends View
|
||||
e.pageX = @renderedLines.offset().left
|
||||
onMouseDown(e)
|
||||
|
||||
@subscribe syntax, 'grammars-loaded', =>
|
||||
@reloadGrammar()
|
||||
for session in @editSessions
|
||||
session.reloadGrammar() unless session is @activeEditSession
|
||||
@subscribe syntax, 'grammars-loaded', => @reloadGrammar()
|
||||
|
||||
@scrollView.on 'scroll', =>
|
||||
if @scrollView.scrollLeft() == 0
|
||||
@@ -481,86 +437,16 @@ class Editor extends View
|
||||
@trigger 'editor:attached', [this]
|
||||
|
||||
edit: (editSession) ->
|
||||
index = @editSessions.indexOf(editSession)
|
||||
index = @pushEditSession(editSession) if index == -1
|
||||
@setActiveEditSessionIndex(index)
|
||||
|
||||
pushEditSession: (editSession) ->
|
||||
index = @editSessions.length
|
||||
@editSessions.push(editSession)
|
||||
@closedEditSessions = @closedEditSessions.filter ({path})->
|
||||
path isnt editSession.getPath()
|
||||
editSession.on 'destroyed', => @editSessionDestroyed(editSession)
|
||||
@trigger 'editor:edit-session-added', [editSession, index]
|
||||
index
|
||||
|
||||
getBuffer: -> @activeEditSession.buffer
|
||||
|
||||
undoDestroySession: ->
|
||||
return unless @closedEditSessions.length > 0
|
||||
|
||||
{path, index} = @closedEditSessions.pop()
|
||||
rootView.open(path)
|
||||
activeIndex = @getActiveEditSessionIndex()
|
||||
@moveEditSessionToIndex(activeIndex, index) if index < activeIndex
|
||||
|
||||
destroyActiveEditSession: ->
|
||||
@destroyEditSessionIndex(@getActiveEditSessionIndex())
|
||||
|
||||
destroyEditSessionIndex: (index, callback) ->
|
||||
return if @mini
|
||||
|
||||
editSession = @editSessions[index]
|
||||
destroySession = =>
|
||||
path = editSession.getPath()
|
||||
@closedEditSessions.push({path, index}) if path
|
||||
editSession.destroy()
|
||||
callback?(index)
|
||||
|
||||
if editSession.isModified() and not editSession.hasEditors()
|
||||
@promptToSaveDirtySession(editSession, destroySession)
|
||||
else
|
||||
destroySession()
|
||||
|
||||
destroyInactiveEditSessions: ->
|
||||
destroyIndex = (index) =>
|
||||
index++ if index is @getActiveEditSessionIndex()
|
||||
@destroyEditSessionIndex(index, destroyIndex) if @editSessions[index]
|
||||
destroyIndex(0)
|
||||
|
||||
destroyAllEditSessions: ->
|
||||
destroyIndex = (index) =>
|
||||
@destroyEditSessionIndex(index, destroyIndex) if @editSessions[index]
|
||||
destroyIndex(0)
|
||||
|
||||
editSessionDestroyed: (editSession) ->
|
||||
index = @editSessions.indexOf(editSession)
|
||||
@loadPreviousEditSession() if index is @getActiveEditSessionIndex() and @editSessions.length > 1
|
||||
_.remove(@editSessions, editSession)
|
||||
@trigger 'editor:edit-session-removed', [editSession, index]
|
||||
@remove() if @editSessions.length is 0
|
||||
|
||||
loadNextEditSession: ->
|
||||
nextIndex = (@getActiveEditSessionIndex() + 1) % @editSessions.length
|
||||
@setActiveEditSessionIndex(nextIndex)
|
||||
|
||||
loadPreviousEditSession: ->
|
||||
previousIndex = @getActiveEditSessionIndex() - 1
|
||||
previousIndex = @editSessions.length - 1 if previousIndex < 0
|
||||
@setActiveEditSessionIndex(previousIndex)
|
||||
|
||||
getActiveEditSessionIndex: ->
|
||||
return index for session, index in @editSessions when session == @activeEditSession
|
||||
|
||||
setActiveEditSessionIndex: (index) ->
|
||||
throw new Error("Edit session not found") unless @editSessions[index]
|
||||
return if editSession is @activeEditSession
|
||||
|
||||
if @activeEditSession
|
||||
@autosave() if config.get "editor.autosave"
|
||||
@saveScrollPositionForActiveEditSession()
|
||||
@activeEditSession.off(".editor")
|
||||
|
||||
@activeEditSession = @editSessions[index]
|
||||
@activeEditSession = editSession
|
||||
|
||||
return unless @activeEditSession?
|
||||
|
||||
@activeEditSession.setVisible(true)
|
||||
|
||||
@activeEditSession.on "contents-conflicted.editor", =>
|
||||
@@ -571,11 +457,18 @@ class Editor extends View
|
||||
@trigger 'editor:path-changed'
|
||||
|
||||
@trigger 'editor:path-changed'
|
||||
@trigger 'editor:active-edit-session-changed', [@activeEditSession, index]
|
||||
@resetDisplay()
|
||||
|
||||
if @attached and @activeEditSession.buffer.isInConflict()
|
||||
setTimeout(( =>@showBufferConflictAlert(@activeEditSession)), 0) # Display after editSession has a chance to display
|
||||
_.defer => @showBufferConflictAlert(@activeEditSession) # Display after editSession has a chance to display
|
||||
|
||||
getModel: ->
|
||||
@activeEditSession
|
||||
|
||||
setModel: (editSession) ->
|
||||
@edit(editSession)
|
||||
|
||||
getBuffer: -> @activeEditSession.buffer
|
||||
|
||||
showBufferConflictAlert: (editSession) ->
|
||||
atom.confirm(
|
||||
@@ -585,30 +478,6 @@ class Editor extends View
|
||||
"Cancel"
|
||||
)
|
||||
|
||||
moveEditSessionToIndex: (fromIndex, toIndex) ->
|
||||
return if fromIndex is toIndex
|
||||
editSession = @editSessions.splice(fromIndex, 1)
|
||||
@editSessions.splice(toIndex, 0, editSession[0])
|
||||
@trigger 'editor:edit-session-order-changed', [editSession, fromIndex, toIndex]
|
||||
@setActiveEditSessionIndex(toIndex)
|
||||
|
||||
moveEditSessionToEditor: (fromIndex, toEditor, toIndex) ->
|
||||
fromEditSession = @editSessions[fromIndex]
|
||||
toEditSession = fromEditSession.copy()
|
||||
@destroyEditSessionIndex(fromIndex)
|
||||
toEditor.edit(toEditSession)
|
||||
toEditor.moveEditSessionToIndex(toEditor.getActiveEditSessionIndex(), toIndex)
|
||||
|
||||
activateEditSessionForPath: (path) ->
|
||||
for editSession, index in @editSessions
|
||||
if editSession.buffer.getPath() == path
|
||||
@setActiveEditSessionIndex(index)
|
||||
return @activeEditSession
|
||||
false
|
||||
|
||||
getOpenBufferPaths: ->
|
||||
editSession.buffer.getPath() for editSession in @editSessions when editSession.buffer.getPath()?
|
||||
|
||||
scrollTop: (scrollTop, options={}) ->
|
||||
return @cachedScrollTop or 0 unless scrollTop?
|
||||
maxScrollTop = @verticalScrollbar.prop('scrollHeight') - @verticalScrollbar.height()
|
||||
@@ -723,22 +592,6 @@ class Editor extends View
|
||||
@removeClass 'soft-wrap'
|
||||
$(window).off 'resize', @_setSoftWrapColumn
|
||||
|
||||
save: (session=@activeEditSession, onSuccess) ->
|
||||
if @getPath()
|
||||
session.save()
|
||||
onSuccess?()
|
||||
else
|
||||
@saveAs(session, onSuccess)
|
||||
|
||||
saveAs: (session=@activeEditSession, onSuccess) ->
|
||||
atom.showSaveDialog (path) =>
|
||||
if path
|
||||
session.saveAs(path)
|
||||
onSuccess?()
|
||||
|
||||
autosave: ->
|
||||
@save() if @getPath()?
|
||||
|
||||
setFontSize: (fontSize) ->
|
||||
headTag = $("head")
|
||||
styleTag = headTag.find("style.font-size")
|
||||
@@ -781,54 +634,33 @@ class Editor extends View
|
||||
@updateLayerDimensions()
|
||||
@requestDisplayUpdate()
|
||||
|
||||
newSplitEditor: (editSession) ->
|
||||
new Editor { editSession: editSession ? @activeEditSession.copy() }
|
||||
splitLeft: (items...) ->
|
||||
@pane()?.splitLeft(items...).activeView
|
||||
|
||||
splitLeft: (editSession) ->
|
||||
@pane()?.splitLeft(@newSplitEditor(editSession)).wrappedView
|
||||
splitRight: (items...) ->
|
||||
@pane()?.splitRight(items...).activeView
|
||||
|
||||
splitRight: (editSession) ->
|
||||
@pane()?.splitRight(@newSplitEditor(editSession)).wrappedView
|
||||
splitUp: (items...) ->
|
||||
@pane()?.splitUp(items...).activeView
|
||||
|
||||
splitUp: (editSession) ->
|
||||
@pane()?.splitUp(@newSplitEditor(editSession)).wrappedView
|
||||
|
||||
splitDown: (editSession) ->
|
||||
@pane()?.splitDown(@newSplitEditor(editSession)).wrappedView
|
||||
splitDown: (items...) ->
|
||||
@pane()?.splitDown(items...).activeView
|
||||
|
||||
pane: ->
|
||||
@parent('.pane').view()
|
||||
|
||||
promptToSaveDirtySession: (session, callback) ->
|
||||
path = session.getPath()
|
||||
filename = if path then fs.base(path) else "untitled buffer"
|
||||
atom.confirm(
|
||||
"'#{filename}' has changes, do you want to save them?"
|
||||
"Your changes will be lost if you don't save them"
|
||||
"Save", => @save(session, callback),
|
||||
"Cancel", null
|
||||
"Don't Save", callback
|
||||
)
|
||||
@closest('.pane').view()
|
||||
|
||||
remove: (selector, keepData) ->
|
||||
return super if keepData or @removed
|
||||
@trigger 'editor:will-be-removed'
|
||||
if @pane() then @pane().remove() else super
|
||||
super
|
||||
rootView?.focus()
|
||||
|
||||
afterRemove: ->
|
||||
@removed = true
|
||||
@destroyEditSessions()
|
||||
@activeEditSession?.destroy()
|
||||
$(window).off(".editor-#{@id}")
|
||||
$(document).off(".editor-#{@id}")
|
||||
|
||||
getEditSessions: ->
|
||||
new Array(@editSessions...)
|
||||
|
||||
destroyEditSessions: ->
|
||||
for session in @getEditSessions()
|
||||
session.destroy()
|
||||
|
||||
getCursorView: (index) ->
|
||||
index ?= @cursorViews.length - 1
|
||||
@cursorViews[index]
|
||||
@@ -933,7 +765,8 @@ class Editor extends View
|
||||
@pendingDisplayUpdate = false
|
||||
|
||||
updateDisplay: (options={}) ->
|
||||
return unless @attached
|
||||
return unless @attached and @activeEditSession
|
||||
return if @activeEditSession.destroyed
|
||||
@updateRenderedLines()
|
||||
@highlightCursorLine()
|
||||
@updateCursorViews()
|
||||
|
||||
@@ -83,4 +83,3 @@ module.exports =
|
||||
for name, handlers of @eventHandlersByEventName
|
||||
count += handlers.length
|
||||
count
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
_ = require 'underscore'
|
||||
fs = require 'fs'
|
||||
Subscriber = require 'subscriber'
|
||||
EventEmitter = require 'event-emitter'
|
||||
GitRepository = require 'git-repository'
|
||||
RepositoryStatusTask = require 'repository-status-task'
|
||||
|
||||
module.exports =
|
||||
class Git
|
||||
|
||||
@open: (path, options) ->
|
||||
return null unless path
|
||||
try
|
||||
new Git(path, options)
|
||||
catch e
|
||||
@@ -23,12 +26,27 @@ class Git
|
||||
working_dir_typechange: 1 << 10
|
||||
ignore: 1 << 14
|
||||
|
||||
statuses: null
|
||||
upstream: null
|
||||
statusTask: null
|
||||
|
||||
constructor: (path, options={}) ->
|
||||
@statuses = {}
|
||||
@upstream = {ahead: 0, behind: 0}
|
||||
@repo = GitRepository.open(path)
|
||||
refreshIndexOnFocus = options.refreshIndexOnFocus ? true
|
||||
if refreshIndexOnFocus
|
||||
refreshOnWindowFocus = options.refreshOnWindowFocus ? true
|
||||
if refreshOnWindowFocus
|
||||
$ = require 'jquery'
|
||||
@subscribe $(window), 'focus', => @refreshIndex()
|
||||
@subscribe $(window), 'focus', =>
|
||||
@refreshIndex()
|
||||
@refreshStatus()
|
||||
|
||||
project?.eachBuffer this, (buffer) =>
|
||||
bufferStatusHandler = =>
|
||||
path = buffer.getPath()
|
||||
@getPathStatus(path) if path
|
||||
@subscribe buffer, 'saved', bufferStatusHandler
|
||||
@subscribe buffer, 'reloaded', bufferStatusHandler
|
||||
|
||||
getRepo: ->
|
||||
unless @repo?
|
||||
@@ -37,27 +55,40 @@ class Git
|
||||
|
||||
refreshIndex: -> @getRepo().refreshIndex()
|
||||
|
||||
getPath: -> @getRepo().getPath()
|
||||
getPath: ->
|
||||
@path ?= fs.absolute(@getRepo().getPath())
|
||||
|
||||
destroy: ->
|
||||
if @statusTask?
|
||||
@statusTask.abort()
|
||||
@statusTask.off()
|
||||
@statusTask = null
|
||||
|
||||
@getRepo().destroy()
|
||||
@repo = null
|
||||
@unsubscribe()
|
||||
|
||||
getWorkingDirectory: ->
|
||||
repoPath = @getPath()
|
||||
repoPath?.substring(0, repoPath.length - 6)
|
||||
@getPath()?.replace(/\/\.git\/?$/, '')
|
||||
|
||||
getHead: ->
|
||||
@getRepo().getHead() ? ''
|
||||
|
||||
getPathStatus: (path) ->
|
||||
pathStatus = @getRepo().getStatus(@relativize(path))
|
||||
currentPathStatus = @statuses[path] ? 0
|
||||
pathStatus = @getRepo().getStatus(@relativize(path)) ? 0
|
||||
if pathStatus > 0
|
||||
@statuses[path] = pathStatus
|
||||
else
|
||||
delete @statuses[path]
|
||||
if currentPathStatus isnt pathStatus
|
||||
@trigger 'status-changed', path, pathStatus
|
||||
pathStatus
|
||||
|
||||
isPathIgnored: (path) ->
|
||||
@getRepo().isIgnored(@relativize(path))
|
||||
|
||||
isStatusModified: (status) ->
|
||||
isStatusModified: (status=0) ->
|
||||
modifiedFlags = @statusFlags.working_dir_modified |
|
||||
@statusFlags.working_dir_delete |
|
||||
@statusFlags.working_dir_typechange |
|
||||
@@ -69,7 +100,7 @@ class Git
|
||||
isPathModified: (path) ->
|
||||
@isStatusModified(@getPathStatus(path))
|
||||
|
||||
isStatusNew: (status) ->
|
||||
isStatusNew: (status=0) ->
|
||||
newFlags = @statusFlags.working_dir_new |
|
||||
@statusFlags.index_new
|
||||
(status & newFlags) > 0
|
||||
@@ -93,7 +124,9 @@ class Git
|
||||
return head
|
||||
|
||||
checkoutHead: (path) ->
|
||||
@getRepo().checkoutHead(@relativize(path))
|
||||
headCheckedOut = @getRepo().checkoutHead(@relativize(path))
|
||||
@getPathStatus(path) if headCheckedOut
|
||||
headCheckedOut
|
||||
|
||||
getDiffStats: (path) ->
|
||||
@getRepo().getDiffStats(@relativize(path)) ? added: 0, deleted: 0
|
||||
@@ -101,4 +134,30 @@ class Git
|
||||
isSubmodule: (path) ->
|
||||
@getRepo().isSubmodule(@relativize(path))
|
||||
|
||||
refreshStatus: ->
|
||||
if @statusTask?
|
||||
@statusTask.off()
|
||||
@statusTask.one 'task-completed', =>
|
||||
@statusTask = null
|
||||
@refreshStatus()
|
||||
else
|
||||
@statusTask = new RepositoryStatusTask(this)
|
||||
@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().getAheadBehindCounts() ? ahead: 0, behind: 0
|
||||
|
||||
getLineDiffs: (path, text) ->
|
||||
@getRepo().getLineDiffs(@relativize(path), text) ? []
|
||||
|
||||
_.extend Git.prototype, Subscriber
|
||||
_.extend Git.prototype, EventEmitter
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
{View, $$, $$$} = require 'space-pen'
|
||||
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
Range = require 'range'
|
||||
Point = require 'point'
|
||||
|
||||
module.exports =
|
||||
class Gutter extends View
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
'body':
|
||||
'meta-s': 'core:save'
|
||||
'meta-S': 'core:save-as'
|
||||
'enter': 'core:confirm'
|
||||
'escape': 'core:cancel'
|
||||
'meta-w': 'core:close'
|
||||
@@ -30,6 +32,26 @@
|
||||
'ctrl-tab': 'window:focus-next-pane'
|
||||
'ctrl-meta-f': 'window:toggle-full-screen'
|
||||
|
||||
'ctrl-|': 'pane:split-right'
|
||||
'ctrl-w v': 'pane:split-right'
|
||||
'ctrl--': 'pane:split-down'
|
||||
'ctrl-w s': 'pane:split-down'
|
||||
|
||||
'meta-{': 'pane:show-previous-item'
|
||||
'meta-}': 'pane:show-next-item'
|
||||
'alt-meta-left': 'pane:show-previous-item'
|
||||
'alt-meta-right': 'pane:show-next-item'
|
||||
'meta-1': 'pane:show-item-1'
|
||||
'meta-2': 'pane:show-item-2'
|
||||
'meta-3': 'pane:show-item-3'
|
||||
'meta-4': 'pane:show-item-4'
|
||||
'meta-5': 'pane:show-item-5'
|
||||
'meta-6': 'pane:show-item-6'
|
||||
'meta-7': 'pane:show-item-7'
|
||||
'meta-8': 'pane:show-item-8'
|
||||
'meta-9': 'pane:show-item-9'
|
||||
'meta-T': 'pane:reopen-closed-item'
|
||||
|
||||
'.tool-panel':
|
||||
'meta-escape': 'tool-panel:unfocus'
|
||||
'escape': 'core:close'
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
'body':
|
||||
'meta-T': 'editor:undo-close-session'
|
||||
|
||||
'.editor':
|
||||
'meta-s': 'editor:save'
|
||||
'meta-S': 'editor:save-as'
|
||||
'enter': 'editor:newline'
|
||||
'meta-enter': 'editor:newline-below'
|
||||
'meta-shift-enter': 'editor:newline-above'
|
||||
@@ -15,26 +10,9 @@
|
||||
'ctrl-{': 'editor:fold-all'
|
||||
'ctrl-}': 'editor:unfold-all'
|
||||
'alt-meta-ctrl-f': 'editor:fold-selection'
|
||||
'ctrl-|': 'editor:split-right'
|
||||
'ctrl-w v': 'editor:split-right'
|
||||
'ctrl--': 'editor:split-down'
|
||||
'ctrl-w s': 'editor:split-down'
|
||||
'shift-tab': 'editor:outdent-selected-rows'
|
||||
'meta-[': 'editor:outdent-selected-rows'
|
||||
'meta-]': 'editor:indent-selected-rows'
|
||||
'meta-{': 'editor:show-previous-buffer'
|
||||
'meta-}': 'editor:show-next-buffer'
|
||||
'alt-meta-left': 'editor:show-previous-buffer'
|
||||
'alt-meta-right': 'editor:show-next-buffer'
|
||||
'meta-1': 'editor:show-buffer-1'
|
||||
'meta-2': 'editor:show-buffer-2'
|
||||
'meta-3': 'editor:show-buffer-3'
|
||||
'meta-4': 'editor:show-buffer-4'
|
||||
'meta-5': 'editor:show-buffer-5'
|
||||
'meta-6': 'editor:show-buffer-6'
|
||||
'meta-7': 'editor:show-buffer-7'
|
||||
'meta-8': 'editor:show-buffer-8'
|
||||
'meta-9': 'editor:show-buffer-9'
|
||||
'meta-/': 'editor:toggle-line-comments'
|
||||
'ctrl-W': 'editor:select-word'
|
||||
'meta-alt-p': 'editor:log-cursor-scope'
|
||||
|
||||
@@ -2,7 +2,7 @@ $ = require 'jquery'
|
||||
{View} = require 'space-pen'
|
||||
|
||||
module.exports =
|
||||
class PaneGrid extends View
|
||||
class PaneAxis extends View
|
||||
@deserialize: ({children}) ->
|
||||
childViews = children.map (child) -> deserialize(child)
|
||||
new this(childViews)
|
||||
@@ -1,9 +1,9 @@
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
PaneGrid = require 'pane-grid'
|
||||
PaneAxis = require 'pane-axis'
|
||||
|
||||
module.exports =
|
||||
class PaneColumn extends PaneGrid
|
||||
class PaneColumn extends PaneAxis
|
||||
@content: ->
|
||||
@div class: 'column'
|
||||
|
||||
|
||||
119
src/app/pane-container.coffee
Normal file
119
src/app/pane-container.coffee
Normal file
@@ -0,0 +1,119 @@
|
||||
{View} = require 'space-pen'
|
||||
Pane = require 'pane'
|
||||
$ = require 'jquery'
|
||||
|
||||
module.exports =
|
||||
class PaneContainer extends View
|
||||
registerDeserializer(this)
|
||||
|
||||
@deserialize: ({root}) ->
|
||||
container = new PaneContainer
|
||||
container.append(deserialize(root)) if root
|
||||
container.removeEmptyPanes()
|
||||
container
|
||||
|
||||
@content: ->
|
||||
@div id: 'panes'
|
||||
|
||||
initialize: ->
|
||||
@destroyedItemStates = []
|
||||
|
||||
serialize: ->
|
||||
deserializer: 'PaneContainer'
|
||||
root: @getRoot()?.serialize()
|
||||
|
||||
focusNextPane: ->
|
||||
panes = @getPanes()
|
||||
if panes.length > 1
|
||||
currentIndex = panes.indexOf(@getFocusedPane())
|
||||
nextIndex = (currentIndex + 1) % panes.length
|
||||
panes[nextIndex].focus()
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
makeNextPaneActive: ->
|
||||
panes = @getPanes()
|
||||
currentIndex = panes.indexOf(@getActivePane())
|
||||
nextIndex = (currentIndex + 1) % panes.length
|
||||
panes[nextIndex].makeActive()
|
||||
|
||||
reopenItem: ->
|
||||
if lastItemState = @destroyedItemStates.pop()
|
||||
if activePane = @getActivePane()
|
||||
activePane.showItem(deserialize(lastItemState))
|
||||
true
|
||||
else
|
||||
@append(new Pane(deserialize(lastItemState)))
|
||||
|
||||
itemDestroyed: (item) ->
|
||||
state = item.serialize?()
|
||||
state.uri ?= item.getUri?()
|
||||
@destroyedItemStates.push(state) if state?
|
||||
|
||||
itemAdded: (item) ->
|
||||
itemUri = item.getUri?()
|
||||
@destroyedItemStates = @destroyedItemStates.filter (itemState) ->
|
||||
itemState.uri isnt itemUri
|
||||
|
||||
getRoot: ->
|
||||
@children().first().view()
|
||||
|
||||
saveAll: ->
|
||||
pane.saveItems() for pane in @getPanes()
|
||||
|
||||
confirmClose: ->
|
||||
deferred = $.Deferred()
|
||||
modifiedItems = []
|
||||
for pane in @getPanes()
|
||||
modifiedItems.push(item) for item in pane.getItems() when item.isModified?()
|
||||
|
||||
cancel = => deferred.reject()
|
||||
saveNextModifiedItem = =>
|
||||
if modifiedItems.length == 0
|
||||
deferred.resolve()
|
||||
else
|
||||
item = modifiedItems.pop()
|
||||
@paneAtIndex(0).promptToSaveItem item, saveNextModifiedItem, cancel
|
||||
|
||||
saveNextModifiedItem()
|
||||
deferred.promise()
|
||||
|
||||
getPanes: ->
|
||||
@find('.pane').views()
|
||||
|
||||
indexOfPane: (pane) ->
|
||||
@getPanes().indexOf(pane.view())
|
||||
|
||||
paneAtIndex: (index) ->
|
||||
@getPanes()[index]
|
||||
|
||||
eachPane: (callback) ->
|
||||
callback(pane) for pane in @getPanes()
|
||||
paneAttached = (e) -> callback($(e.target).view())
|
||||
@on 'pane:attached', paneAttached
|
||||
cancel: => @off 'pane:attached', paneAttached
|
||||
|
||||
getFocusedPane: ->
|
||||
@find('.pane:has(:focus)').view()
|
||||
|
||||
getActivePane: ->
|
||||
@find('.pane.active').view() ? @find('.pane:first').view()
|
||||
|
||||
getActivePaneItem: ->
|
||||
@getActivePane()?.activeItem
|
||||
|
||||
getActiveView: ->
|
||||
@getActivePane()?.activeView
|
||||
|
||||
adjustPaneDimensions: ->
|
||||
if root = @getRoot()
|
||||
root.css(width: '100%', height: '100%', top: 0, left: 0)
|
||||
root.adjustDimensions()
|
||||
|
||||
removeEmptyPanes: ->
|
||||
for pane in @getPanes() when pane.getItems().length == 0
|
||||
pane.remove()
|
||||
|
||||
afterAttach: ->
|
||||
@adjustPaneDimensions()
|
||||
@@ -1,9 +1,9 @@
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
PaneGrid = require 'pane-grid'
|
||||
PaneAxis = require 'pane-axis'
|
||||
|
||||
module.exports =
|
||||
class PaneRow extends PaneGrid
|
||||
class PaneRow extends PaneAxis
|
||||
@content: ->
|
||||
@div class: 'row'
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
{View} = require 'space-pen'
|
||||
$ = require 'jquery'
|
||||
_ = require 'underscore'
|
||||
PaneRow = require 'pane-row'
|
||||
PaneColumn = require 'pane-column'
|
||||
|
||||
@@ -6,58 +8,321 @@ module.exports =
|
||||
class Pane extends View
|
||||
@content: (wrappedView) ->
|
||||
@div class: 'pane', =>
|
||||
@subview 'wrappedView', wrappedView if wrappedView
|
||||
@div class: 'item-views', outlet: 'itemViews'
|
||||
|
||||
@deserialize: ({wrappedView}) ->
|
||||
new Pane(deserialize(wrappedView))
|
||||
@deserialize: ({items, focused, activeItemUri}) ->
|
||||
deserializedItems = _.compact(items.map((item) -> deserialize(item)))
|
||||
pane = new Pane(deserializedItems...)
|
||||
pane.showItemForUri(activeItemUri) if activeItemUri
|
||||
pane.focusOnAttach = true if focused
|
||||
pane
|
||||
|
||||
activeItem: null
|
||||
items: null
|
||||
|
||||
initialize: (@items...) ->
|
||||
@viewsByClassName = {}
|
||||
@showItem(@items[0]) if @items.length > 0
|
||||
|
||||
@command 'core:close', @destroyActiveItem
|
||||
@command 'core:save', @saveActiveItem
|
||||
@command 'core:save-as', @saveActiveItemAs
|
||||
@command 'pane:save-items', @saveItems
|
||||
@command 'pane:show-next-item', @showNextItem
|
||||
@command 'pane:show-previous-item', @showPreviousItem
|
||||
|
||||
@command 'pane:show-item-1', => @showItemAtIndex(0)
|
||||
@command 'pane:show-item-2', => @showItemAtIndex(1)
|
||||
@command 'pane:show-item-3', => @showItemAtIndex(2)
|
||||
@command 'pane:show-item-4', => @showItemAtIndex(3)
|
||||
@command 'pane:show-item-5', => @showItemAtIndex(4)
|
||||
@command 'pane:show-item-6', => @showItemAtIndex(5)
|
||||
@command 'pane:show-item-7', => @showItemAtIndex(6)
|
||||
@command 'pane:show-item-8', => @showItemAtIndex(7)
|
||||
@command 'pane:show-item-9', => @showItemAtIndex(8)
|
||||
|
||||
@command 'pane:split-left', => @splitLeft()
|
||||
@command 'pane:split-right', => @splitRight()
|
||||
@command 'pane:split-up', => @splitUp()
|
||||
@command 'pane:split-down', => @splitDown()
|
||||
@command 'pane:close', => @destroyItems()
|
||||
@command 'pane:close-other-items', => @destroyInactiveItems()
|
||||
@on 'focus', => @activeView?.focus(); false
|
||||
@on 'focusin', => @makeActive()
|
||||
@on 'focusout', => @autosaveActiveItem()
|
||||
|
||||
afterAttach: (onDom) ->
|
||||
if @focusOnAttach and onDom
|
||||
@focusOnAttach = null
|
||||
@focus()
|
||||
|
||||
return if @attached
|
||||
@attached = true
|
||||
@trigger 'pane:attached'
|
||||
|
||||
makeActive: ->
|
||||
for pane in @getContainer().getPanes() when pane isnt this
|
||||
pane.makeInactive()
|
||||
wasActive = @isActive()
|
||||
@addClass('active')
|
||||
@trigger 'pane:became-active' unless wasActive
|
||||
|
||||
makeInactive: ->
|
||||
@removeClass('active')
|
||||
|
||||
isActive: ->
|
||||
@hasClass('active')
|
||||
|
||||
getNextPane: ->
|
||||
panes = @getContainer()?.getPanes()
|
||||
return unless panes.length > 1
|
||||
nextIndex = (panes.indexOf(this) + 1) % panes.length
|
||||
panes[nextIndex]
|
||||
|
||||
getItems: ->
|
||||
new Array(@items...)
|
||||
|
||||
showNextItem: =>
|
||||
index = @getActiveItemIndex()
|
||||
if index < @items.length - 1
|
||||
@showItemAtIndex(index + 1)
|
||||
else
|
||||
@showItemAtIndex(0)
|
||||
|
||||
showPreviousItem: =>
|
||||
index = @getActiveItemIndex()
|
||||
if index > 0
|
||||
@showItemAtIndex(index - 1)
|
||||
else
|
||||
@showItemAtIndex(@items.length - 1)
|
||||
|
||||
getActiveItemIndex: ->
|
||||
@items.indexOf(@activeItem)
|
||||
|
||||
showItemAtIndex: (index) ->
|
||||
@showItem(@itemAtIndex(index))
|
||||
|
||||
itemAtIndex: (index) ->
|
||||
@items[index]
|
||||
|
||||
showItem: (item) ->
|
||||
return if !item? or item is @activeItem
|
||||
|
||||
if @activeItem
|
||||
@activeItem.off? 'title-changed', @activeItemTitleChanged
|
||||
@autosaveActiveItem()
|
||||
|
||||
isFocused = @is(':has(:focus)')
|
||||
@addItem(item)
|
||||
item.on? 'title-changed', @activeItemTitleChanged
|
||||
view = @viewForItem(item)
|
||||
@itemViews.children().not(view).hide()
|
||||
@itemViews.append(view) unless view.parent().is(@itemViews)
|
||||
view.show()
|
||||
view.focus() if isFocused
|
||||
@activeItem = item
|
||||
@activeView = view
|
||||
@trigger 'pane:active-item-changed', [item]
|
||||
|
||||
activeItemTitleChanged: =>
|
||||
@trigger 'pane:active-item-title-changed'
|
||||
|
||||
addItem: (item) ->
|
||||
return if _.include(@items, item)
|
||||
index = @getActiveItemIndex() + 1
|
||||
@items.splice(index, 0, item)
|
||||
@getContainer().itemAdded(item)
|
||||
@trigger 'pane:item-added', [item, index]
|
||||
item
|
||||
|
||||
destroyActiveItem: =>
|
||||
@destroyItem(@activeItem)
|
||||
false
|
||||
|
||||
destroyItem: (item) ->
|
||||
container = @getContainer()
|
||||
reallyDestroyItem = =>
|
||||
@removeItem(item)
|
||||
container.itemDestroyed(item)
|
||||
item.destroy?()
|
||||
|
||||
@autosaveItem(item)
|
||||
|
||||
if item.isModified?()
|
||||
@promptToSaveItem(item, reallyDestroyItem)
|
||||
else
|
||||
reallyDestroyItem()
|
||||
|
||||
destroyItems: ->
|
||||
@destroyItem(item) for item in @getItems()
|
||||
|
||||
destroyInactiveItems: ->
|
||||
@destroyItem(item) for item in @getItems() when item isnt @activeItem
|
||||
|
||||
promptToSaveItem: (item, nextAction, cancelAction) ->
|
||||
uri = item.getUri()
|
||||
atom.confirm(
|
||||
"'#{item.getTitle?() ? item.getUri()}' has changes, do you want to save them?"
|
||||
"Your changes will be lost if close this item without saving."
|
||||
"Save", => @saveItem(item, nextAction)
|
||||
"Cancel", cancelAction
|
||||
"Don't Save", nextAction
|
||||
)
|
||||
|
||||
saveActiveItem: =>
|
||||
@saveItem(@activeItem)
|
||||
|
||||
saveActiveItemAs: =>
|
||||
@saveItemAs(@activeItem)
|
||||
|
||||
saveItem: (item, nextAction) ->
|
||||
if item.getUri?()
|
||||
item.save()
|
||||
nextAction?()
|
||||
else
|
||||
@saveItemAs(item, nextAction)
|
||||
|
||||
saveItemAs: (item, nextAction) ->
|
||||
return unless item.saveAs?
|
||||
atom.showSaveDialog (path) =>
|
||||
if path
|
||||
item.saveAs(path)
|
||||
nextAction?()
|
||||
|
||||
saveItems: =>
|
||||
@saveItem(item) for item in @getItems()
|
||||
|
||||
autosaveActiveItem: ->
|
||||
@autosaveItem(@activeItem)
|
||||
|
||||
autosaveItem: (item) ->
|
||||
@saveItem(item) if config.get('core.autosave') and item.getUri?()
|
||||
|
||||
removeItem: (item) ->
|
||||
index = @items.indexOf(item)
|
||||
return if index == -1
|
||||
|
||||
@showNextItem() if item is @activeItem and @items.length > 1
|
||||
_.remove(@items, item)
|
||||
@cleanupItemView(item)
|
||||
@trigger 'pane:item-removed', [item, index]
|
||||
|
||||
moveItem: (item, newIndex) ->
|
||||
oldIndex = @items.indexOf(item)
|
||||
@items.splice(oldIndex, 1)
|
||||
@items.splice(newIndex, 0, item)
|
||||
@trigger 'pane:item-moved', [item, newIndex]
|
||||
|
||||
moveItemToPane: (item, pane, index) ->
|
||||
@removeItem(item)
|
||||
pane.addItem(item, index)
|
||||
|
||||
itemForUri: (uri) ->
|
||||
_.detect @items, (item) -> item.getUri?() is uri
|
||||
|
||||
showItemForUri: (uri) ->
|
||||
@showItem(@itemForUri(uri))
|
||||
|
||||
cleanupItemView: (item) ->
|
||||
if item instanceof $
|
||||
viewToRemove = item
|
||||
else
|
||||
viewClass = item.getViewClass()
|
||||
otherItemsForView = @items.filter (i) -> i.getViewClass?() is viewClass
|
||||
unless otherItemsForView.length
|
||||
viewToRemove = @viewsByClassName[viewClass.name]
|
||||
viewToRemove?.setModel(null)
|
||||
delete @viewsByClassName[viewClass.name]
|
||||
|
||||
if @items.length > 0
|
||||
viewToRemove?.remove()
|
||||
else
|
||||
@remove()
|
||||
|
||||
viewForItem: (item) ->
|
||||
if item instanceof $
|
||||
item
|
||||
else
|
||||
viewClass = item.getViewClass()
|
||||
if view = @viewsByClassName[viewClass.name]
|
||||
view.setModel(item)
|
||||
else
|
||||
view = @viewsByClassName[viewClass.name] = new viewClass(item)
|
||||
view
|
||||
|
||||
viewForActiveItem: ->
|
||||
@viewForItem(@activeItem)
|
||||
|
||||
serialize: ->
|
||||
deserializer: "Pane"
|
||||
wrappedView: @wrappedView?.serialize()
|
||||
focused: @is(':has(:focus)')
|
||||
activeItemUri: @activeItem.getUri?() if typeof @activeItem.serialize is 'function'
|
||||
items: _.compact(@getItems().map (item) -> item.serialize?())
|
||||
|
||||
adjustDimensions: -> # do nothing
|
||||
|
||||
horizontalGridUnits: ->
|
||||
1
|
||||
horizontalGridUnits: -> 1
|
||||
|
||||
verticalGridUnits: ->
|
||||
1
|
||||
verticalGridUnits: -> 1
|
||||
|
||||
splitUp: (view) ->
|
||||
@split(view, 'column', 'before')
|
||||
splitUp: (items...) ->
|
||||
@split(items, 'column', 'before')
|
||||
|
||||
splitDown: (view) ->
|
||||
@split(view, 'column', 'after')
|
||||
splitDown: (items...) ->
|
||||
@split(items, 'column', 'after')
|
||||
|
||||
splitLeft: (view) ->
|
||||
@split(view, 'row', 'before')
|
||||
splitLeft: (items...) ->
|
||||
@split(items, 'row', 'before')
|
||||
|
||||
splitRight: (view) ->
|
||||
@split(view, 'row', 'after')
|
||||
splitRight: (items...) ->
|
||||
@split(items, 'row', 'after')
|
||||
|
||||
split: (view, axis, side) ->
|
||||
split: (items, axis, side) ->
|
||||
unless @parent().hasClass(axis)
|
||||
@buildPaneAxis(axis)
|
||||
.insertBefore(this)
|
||||
.append(@detach())
|
||||
|
||||
pane = new Pane(view)
|
||||
items = [@copyActiveItem()] unless items.length
|
||||
pane = new Pane(items...)
|
||||
this[side](pane)
|
||||
rootView.adjustPaneDimensions()
|
||||
view.focus?()
|
||||
@getContainer().adjustPaneDimensions()
|
||||
pane.focus()
|
||||
pane
|
||||
|
||||
remove: (selector, keepData) ->
|
||||
return super if keepData
|
||||
# find parent elements before removing from dom
|
||||
parentAxis = @parent('.row, .column')
|
||||
super
|
||||
if parentAxis.children().length == 1
|
||||
sibling = parentAxis.children().detach()
|
||||
parentAxis.replaceWith(sibling)
|
||||
rootView.adjustPaneDimensions()
|
||||
|
||||
buildPaneAxis: (axis) ->
|
||||
switch axis
|
||||
when 'row' then new PaneRow
|
||||
when 'column' then new PaneColumn
|
||||
|
||||
getContainer: ->
|
||||
@closest('#panes').view()
|
||||
|
||||
copyActiveItem: ->
|
||||
deserialize(@activeItem.serialize())
|
||||
|
||||
remove: (selector, keepData) ->
|
||||
return super if keepData
|
||||
|
||||
# find parent elements before removing from dom
|
||||
container = @getContainer()
|
||||
parentAxis = @parent('.row, .column')
|
||||
|
||||
if @is(':has(:focus)')
|
||||
container.focusNextPane() or rootView?.focus()
|
||||
else if @isActive()
|
||||
container.makeNextPaneActive()
|
||||
|
||||
super
|
||||
|
||||
if parentAxis.children().length == 1
|
||||
sibling = parentAxis.children()
|
||||
siblingFocused = sibling.is(':has(:focus)')
|
||||
sibling.detach()
|
||||
parentAxis.replaceWith(sibling)
|
||||
sibling.focus() if siblingFocused
|
||||
container.adjustPaneDimensions()
|
||||
container.trigger 'pane:removed', [this]
|
||||
|
||||
afterRemove: ->
|
||||
item.destroy?() for item in @getItems()
|
||||
|
||||
@@ -7,7 +7,6 @@ EditSession = require 'edit-session'
|
||||
EventEmitter = require 'event-emitter'
|
||||
Directory = require 'directory'
|
||||
ChildProcess = require 'child-process'
|
||||
Git = require 'git'
|
||||
|
||||
module.exports =
|
||||
class Project
|
||||
@@ -35,8 +34,6 @@ class Project
|
||||
grammarOverridesByPath: @grammarOverridesByPath
|
||||
|
||||
destroy: ->
|
||||
@repo?.destroy()
|
||||
@repo = null
|
||||
editSession.destroy() for editSession in @getEditSessions()
|
||||
|
||||
addGrammarOverrideForPath: (path, grammar) ->
|
||||
@@ -60,10 +57,8 @@ class Project
|
||||
if path?
|
||||
directory = if fs.isDirectory(path) then path else fs.directory(path)
|
||||
@rootDirectory = new Directory(directory)
|
||||
@repo = Git.open(path)
|
||||
else
|
||||
@rootDirectory = null
|
||||
@repo = null
|
||||
|
||||
@trigger "path-changed"
|
||||
|
||||
@@ -85,7 +80,7 @@ class Project
|
||||
@ignoreRepositoryPath(path)
|
||||
|
||||
ignoreRepositoryPath: (path) ->
|
||||
config.get("core.hideGitIgnoredFiles") and @repo?.isPathIgnored(fs.join(@getPath(), path))
|
||||
config.get("core.hideGitIgnoredFiles") and git?.isPathIgnored(fs.join(@getPath(), path))
|
||||
|
||||
resolve: (filePath) ->
|
||||
filePath = fs.join(@getPath(), filePath) unless filePath[0] == '/'
|
||||
@@ -100,10 +95,10 @@ class Project
|
||||
getSoftWrap: -> @softWrap
|
||||
setSoftWrap: (@softWrap) ->
|
||||
|
||||
buildEditSessionForPath: (filePath, editSessionOptions={}) ->
|
||||
@buildEditSession(@bufferForPath(filePath), editSessionOptions)
|
||||
buildEditSession: (filePath, editSessionOptions={}) ->
|
||||
@buildEditSessionForBuffer(@bufferForPath(filePath), editSessionOptions)
|
||||
|
||||
buildEditSession: (buffer, editSessionOptions) ->
|
||||
buildEditSessionForBuffer: (buffer, editSessionOptions) ->
|
||||
options = _.extend(@defaultEditSessionOptions(), editSessionOptions)
|
||||
options.project = this
|
||||
options.buffer = buffer
|
||||
@@ -133,9 +128,15 @@ class Project
|
||||
buffers.push editSession.buffer
|
||||
buffers
|
||||
|
||||
eachBuffer: (callback) ->
|
||||
eachBuffer: (args...) ->
|
||||
subscriber = args.shift() if args.length > 1
|
||||
callback = args.shift()
|
||||
|
||||
callback(buffer) for buffer in @getBuffers()
|
||||
@on 'buffer-created', (buffer) -> callback(buffer)
|
||||
if subscriber
|
||||
subscriber.subscribe this, 'buffer-created', (buffer) -> callback(buffer)
|
||||
else
|
||||
@on 'buffer-created', (buffer) -> callback(buffer)
|
||||
|
||||
bufferForPath: (filePath) ->
|
||||
if filePath?
|
||||
|
||||
18
src/app/repository-status-handler.coffee
Normal file
18
src/app/repository-status-handler.coffee
Normal file
@@ -0,0 +1,18 @@
|
||||
Git = require 'git'
|
||||
fs = require 'fs'
|
||||
|
||||
module.exports =
|
||||
loadStatuses: (path) ->
|
||||
repo = Git.open(path)
|
||||
if repo?
|
||||
workingDirectoryPath = repo.getWorkingDirectory()
|
||||
statuses = {}
|
||||
for path, status of repo.getRepo().getStatuses()
|
||||
statuses[fs.join(workingDirectoryPath, path)] = status
|
||||
upstream = repo.getAheadBehindCounts()
|
||||
repo.destroy()
|
||||
else
|
||||
upstream = {}
|
||||
statuses = {}
|
||||
|
||||
callTaskMethod('statusesLoaded', {statuses, upstream})
|
||||
17
src/app/repository-status-task.coffee
Normal file
17
src/app/repository-status-task.coffee
Normal file
@@ -0,0 +1,17 @@
|
||||
Task = require 'task'
|
||||
_ = require 'underscore'
|
||||
|
||||
module.exports =
|
||||
class RepositoryStatusTask extends Task
|
||||
constructor: (@repo) ->
|
||||
super('repository-status-handler')
|
||||
|
||||
started: ->
|
||||
@callWorkerMethod('loadStatuses', @repo.getPath())
|
||||
|
||||
statusesLoaded: ({statuses, upstream}) ->
|
||||
@done()
|
||||
statusesUnchanged = _.isEqual(statuses, @repo.statuses) and _.isEqual(upstream, @repo.upstream)
|
||||
@repo.statuses = statuses
|
||||
@repo.upstream = upstream
|
||||
@repo.trigger 'statuses-changed' unless statusesUnchanged
|
||||
@@ -10,28 +10,29 @@ Project = require 'project'
|
||||
Pane = require 'pane'
|
||||
PaneColumn = require 'pane-column'
|
||||
PaneRow = require 'pane-row'
|
||||
PaneContainer = require 'pane-container'
|
||||
EditSession = require 'edit-session'
|
||||
|
||||
module.exports =
|
||||
class RootView extends View
|
||||
registerDeserializers(this, Pane, PaneRow, PaneColumn, Editor)
|
||||
|
||||
@version: 1
|
||||
|
||||
@configDefaults:
|
||||
ignoredNames: [".git", ".svn", ".DS_Store"]
|
||||
disabledPackages: []
|
||||
|
||||
@content: ->
|
||||
@content: ({panes}={}) ->
|
||||
@div id: 'root-view', =>
|
||||
@div id: 'horizontal', outlet: 'horizontal', =>
|
||||
@div id: 'vertical', outlet: 'vertical', =>
|
||||
@div id: 'panes', outlet: 'panes'
|
||||
@subview 'panes', panes ? new PaneContainer
|
||||
|
||||
@deserialize: ({ panesViewState, packageStates, projectPath }) ->
|
||||
atom.atomPackageStates = packageStates ? {}
|
||||
rootView = new RootView
|
||||
rootView.setRootPane(deserialize(panesViewState)) if panesViewState
|
||||
rootView
|
||||
|
||||
title: null
|
||||
@deserialize: ({ panes, packages, projectPath }) ->
|
||||
atom.atomPackageStates = packages ? {}
|
||||
panes = deserialize(panes) if panes?.deserializer is 'PaneContainer'
|
||||
new RootView({panes})
|
||||
|
||||
initialize: ->
|
||||
@command 'toggle-dev-tools', => atom.toggleDevTools()
|
||||
@@ -39,12 +40,11 @@ class RootView extends View
|
||||
@subscribe $(window), 'focus', (e) =>
|
||||
@handleFocus(e) if document.activeElement is document.body
|
||||
|
||||
@on 'root-view:active-path-changed', (e, path) =>
|
||||
if path
|
||||
project.setPath(path) unless project.getRootDirectory()
|
||||
@setTitle(fs.base(path))
|
||||
else
|
||||
@setTitle("untitled")
|
||||
project.on 'path-changed', => @updateTitle()
|
||||
@on 'pane:became-active', => @updateTitle()
|
||||
@on 'pane:active-item-changed', '.active.pane', => @updateTitle()
|
||||
@on 'pane:removed', => @updateTitle() unless @getActivePane()
|
||||
@on 'pane:active-item-title-changed', '.active.pane', => @updateTitle()
|
||||
|
||||
@command 'window:increase-font-size', =>
|
||||
config.set("editor.fontSize", config.get("editor.fontSize") + 1)
|
||||
@@ -53,26 +53,34 @@ class RootView extends View
|
||||
fontSize = config.get "editor.fontSize"
|
||||
config.set("editor.fontSize", fontSize - 1) if fontSize > 1
|
||||
|
||||
|
||||
@command 'window:focus-next-pane', => @focusNextPane()
|
||||
@command 'window:save-all', => @saveAll()
|
||||
@command 'window:toggle-invisibles', =>
|
||||
config.set("editor.showInvisibles", !config.get("editor.showInvisibles"))
|
||||
@command 'window:toggle-ignored-files', =>
|
||||
config.set("core.hideGitIgnoredFiles", not config.core.hideGitIgnoredFiles)
|
||||
|
||||
@command 'window:toggle-auto-indent', =>
|
||||
config.set("editor.autoIndent", !config.get("editor.autoIndent"))
|
||||
|
||||
@command 'window:toggle-auto-indent-on-paste', =>
|
||||
config.set("editor.autoIndentOnPaste", !config.get("editor.autoIndentOnPaste"))
|
||||
|
||||
@command 'pane:reopen-closed-item', =>
|
||||
@panes.reopenItem()
|
||||
|
||||
serialize: ->
|
||||
version: RootView.version
|
||||
deserializer: 'RootView'
|
||||
panesViewState: @panes.children().view()?.serialize()
|
||||
packageStates: atom.serializeAtomPackages()
|
||||
panes: @panes.serialize()
|
||||
packages: atom.serializeAtomPackages()
|
||||
|
||||
confirmClose: ->
|
||||
@panes.confirmClose()
|
||||
|
||||
handleFocus: (e) ->
|
||||
if @getActiveEditor()
|
||||
@getActiveEditor().focus()
|
||||
if @getActivePane()
|
||||
@getActivePane().focus()
|
||||
false
|
||||
else
|
||||
@setTitle(null)
|
||||
@@ -92,118 +100,57 @@ class RootView extends View
|
||||
|
||||
open: (path, options = {}) ->
|
||||
changeFocus = options.changeFocus ? true
|
||||
allowActiveEditorChange = options.allowActiveEditorChange ? false
|
||||
|
||||
unless editSession = @openInExistingEditor(path, allowActiveEditorChange, changeFocus)
|
||||
editSession = project.buildEditSessionForPath(path)
|
||||
editor = new Editor({editSession})
|
||||
pane = new Pane(editor)
|
||||
@panes.append(pane)
|
||||
if changeFocus
|
||||
editor.focus()
|
||||
path = project.resolve(path) if path?
|
||||
if activePane = @getActivePane()
|
||||
if editSession = activePane.itemForUri(path)
|
||||
activePane.showItem(editSession)
|
||||
else
|
||||
@makeEditorActive(editor, changeFocus)
|
||||
editSession = project.buildEditSession(path)
|
||||
activePane.showItem(editSession)
|
||||
else
|
||||
editSession = project.buildEditSession(path)
|
||||
activePane = new Pane(editSession)
|
||||
@panes.append(activePane)
|
||||
|
||||
activePane.focus() if changeFocus
|
||||
editSession
|
||||
|
||||
openInExistingEditor: (path, allowActiveEditorChange, changeFocus) ->
|
||||
if activeEditor = @getActiveEditor()
|
||||
activeEditor.focus() if changeFocus
|
||||
|
||||
path = project.resolve(path) if path
|
||||
|
||||
if editSession = activeEditor.activateEditSessionForPath(path)
|
||||
return editSession
|
||||
|
||||
if allowActiveEditorChange
|
||||
for editor in @getEditors()
|
||||
if editSession = editor.activateEditSessionForPath(path)
|
||||
@makeEditorActive(editor, changeFocus)
|
||||
return editSession
|
||||
|
||||
editSession = project.buildEditSessionForPath(path)
|
||||
activeEditor.edit(editSession)
|
||||
editSession
|
||||
|
||||
editorFocused: (editor) ->
|
||||
@makeEditorActive(editor) if @panes.containsElement(editor)
|
||||
|
||||
makeEditorActive: (editor, focus) ->
|
||||
if focus
|
||||
editor.focus()
|
||||
return
|
||||
|
||||
previousActiveEditor = @panes.find('.editor.active').view()
|
||||
previousActiveEditor?.removeClass('active').off('.root-view')
|
||||
editor.addClass('active')
|
||||
|
||||
if not editor.mini
|
||||
editor.on 'editor:path-changed.root-view', =>
|
||||
@trigger 'root-view:active-path-changed', editor.getPath()
|
||||
if not previousActiveEditor or editor.getPath() != previousActiveEditor.getPath()
|
||||
@trigger 'root-view:active-path-changed', editor.getPath()
|
||||
|
||||
activeKeybindings: ->
|
||||
keymap.bindingsForElement(document.activeElement)
|
||||
|
||||
getTitle: ->
|
||||
@title or "untitled"
|
||||
updateTitle: ->
|
||||
if projectPath = project.getPath()
|
||||
if item = @getActivePaneItem()
|
||||
@setTitle("#{item.getTitle?() ? 'untitled'} - #{projectPath}")
|
||||
else
|
||||
@setTitle(projectPath)
|
||||
else
|
||||
@setTitle('untitled')
|
||||
|
||||
setTitle: (title) ->
|
||||
projectPath = project.getPath()
|
||||
if not projectPath
|
||||
@title = "untitled"
|
||||
else if title
|
||||
@title = "#{title} – #{projectPath}"
|
||||
else
|
||||
@title = projectPath
|
||||
|
||||
@updateWindowTitle()
|
||||
|
||||
updateWindowTitle: ->
|
||||
document.title = @title
|
||||
document.title = title
|
||||
|
||||
getEditors: ->
|
||||
@panes.find('.pane > .editor').map(-> $(this).view()).toArray()
|
||||
@panes.find('.pane > .item-views > .editor').map(-> $(this).view()).toArray()
|
||||
|
||||
getModifiedBuffers: ->
|
||||
modifiedBuffers = []
|
||||
for editor in @getEditors()
|
||||
for session in editor.editSessions
|
||||
modifiedBuffers.push session.buffer if session.buffer.isModified()
|
||||
|
||||
for pane in @getPanes()
|
||||
for item in pane.getItems() when item instanceof EditSession
|
||||
modifiedBuffers.push item.buffer if item.buffer.isModified()
|
||||
modifiedBuffers
|
||||
|
||||
getOpenBufferPaths: ->
|
||||
_.uniq(_.flatten(@getEditors().map (editor) -> editor.getOpenBufferPaths()))
|
||||
|
||||
getActiveEditor: ->
|
||||
if (editor = @panes.find('.editor.active')).length
|
||||
editor.view()
|
||||
else
|
||||
@panes.find('.editor:first').view()
|
||||
getActivePane: ->
|
||||
@panes.getActivePane()
|
||||
|
||||
getActiveEditSession: ->
|
||||
@getActiveEditor()?.activeEditSession
|
||||
getActivePaneItem: ->
|
||||
@panes.getActivePaneItem()
|
||||
|
||||
focusNextPane: ->
|
||||
panes = @panes.find('.pane')
|
||||
currentIndex = panes.toArray().indexOf(@getFocusedPane()[0])
|
||||
nextIndex = (currentIndex + 1) % panes.length
|
||||
panes.eq(nextIndex).view().wrappedView.focus()
|
||||
getActiveView: ->
|
||||
@panes.getActiveView()
|
||||
|
||||
getFocusedPane: ->
|
||||
@panes.find('.pane:has(:focus)')
|
||||
|
||||
setRootPane: (pane) ->
|
||||
@panes.empty()
|
||||
@panes.append(pane)
|
||||
@adjustPaneDimensions()
|
||||
|
||||
adjustPaneDimensions: ->
|
||||
rootPane = @panes.children().first().view()
|
||||
rootPane?.css(width: '100%', height: '100%', top: 0, left: 0)
|
||||
rootPane?.adjustDimensions()
|
||||
focusNextPane: -> @panes.focusNextPane()
|
||||
getFocusedPane: -> @panes.getFocusedPane()
|
||||
|
||||
remove: ->
|
||||
editor.remove() for editor in @getEditors()
|
||||
@@ -211,7 +158,16 @@ class RootView extends View
|
||||
super
|
||||
|
||||
saveAll: ->
|
||||
editor.save() for editor in @getEditors()
|
||||
@panes.saveAll()
|
||||
|
||||
eachPane: (callback) ->
|
||||
@panes.eachPane(callback)
|
||||
|
||||
getPanes: ->
|
||||
@panes.getPanes()
|
||||
|
||||
indexOfPane: (pane) ->
|
||||
@panes.indexOfPane(pane)
|
||||
|
||||
eachEditor: (callback) ->
|
||||
callback(editor) for editor in @getEditors()
|
||||
@@ -223,10 +179,3 @@ class RootView extends View
|
||||
eachBuffer: (callback) ->
|
||||
project.eachBuffer(callback)
|
||||
|
||||
indexOfPane: (pane) ->
|
||||
index = -1
|
||||
for p, idx in @panes.find('.pane')
|
||||
if pane.is(p)
|
||||
index = idx
|
||||
break
|
||||
index
|
||||
|
||||
@@ -12,6 +12,8 @@ class ScreenLine
|
||||
new ScreenLine({@tokens, @ruleStack, @bufferRows, @startBufferColumn, @fold})
|
||||
|
||||
clipScreenColumn: (column, options={}) ->
|
||||
return 0 if @tokens.length == 0
|
||||
|
||||
{ skipAtomicTokens } = options
|
||||
column = Math.min(column, @getMaxScreenColumn())
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class SelectList extends View
|
||||
cancelling: false
|
||||
|
||||
initialize: ->
|
||||
requireStylesheet 'select-list.css'
|
||||
requireStylesheet 'select-list.less'
|
||||
|
||||
@miniEditor.getBuffer().on 'changed', => @schedulePopulateList()
|
||||
@miniEditor.on 'focusout', => @cancel() unless @cancelling
|
||||
|
||||
@@ -164,7 +164,7 @@ class Selection
|
||||
if options.autoIndent
|
||||
if text == '\n'
|
||||
@editSession.autoIndentBufferRow(newBufferRange.end.row)
|
||||
else
|
||||
else if /\S/.test(text)
|
||||
@editSession.autoDecreaseIndentForRow(newBufferRange.start.row)
|
||||
|
||||
newBufferRange
|
||||
|
||||
@@ -14,7 +14,9 @@ class SortableList extends View
|
||||
@on 'drop', '.sortable', @onDrop
|
||||
|
||||
onDragStart: (event) =>
|
||||
return false if !@shouldAllowDrag(event)
|
||||
unless @shouldAllowDrag(event)
|
||||
event.preventDefault()
|
||||
return
|
||||
|
||||
el = @getSortableElement(event)
|
||||
el.addClass 'is-dragging'
|
||||
@@ -45,9 +47,8 @@ class SortableList extends View
|
||||
true
|
||||
|
||||
getDroppedElement: (event) ->
|
||||
idx = event.originalEvent.dataTransfer.getData 'sortable-index'
|
||||
@find ".sortable:eq(#{idx})"
|
||||
index = event.originalEvent.dataTransfer.getData('sortable-index')
|
||||
@find(".sortable:eq(#{index})")
|
||||
|
||||
getSortableElement: (event) ->
|
||||
el = $(event.target)
|
||||
if !el.hasClass('sortable') then el.closest('.sortable') else el
|
||||
$(event.target).closest('.sortable')
|
||||
|
||||
@@ -31,6 +31,8 @@ class TextMatePackage extends Package
|
||||
console.warn "Failed to load package at '#{@path}'", e.stack
|
||||
this
|
||||
|
||||
activate: -> # no-op
|
||||
|
||||
getGrammars: -> @grammars
|
||||
|
||||
readGrammars: ->
|
||||
|
||||
@@ -11,7 +11,7 @@ class Theme
|
||||
if fs.exists(name)
|
||||
path = name
|
||||
else
|
||||
path = fs.resolve(config.themeDirPaths..., name, ['', '.tmTheme', '.css'])
|
||||
path = fs.resolve(config.themeDirPaths..., name, ['', '.tmTheme', '.css', 'less'])
|
||||
|
||||
throw new Error("No theme exists named '#{name}'") unless path
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user